summaryrefslogtreecommitdiff
path: root/vendor
diff options
context:
space:
mode:
Diffstat (limited to 'vendor')
-rw-r--r--vendor/autoload.php7
-rw-r--r--vendor/composer/ClassLoader.php413
-rw-r--r--vendor/composer/autoload_classmap.php85
-rw-r--r--vendor/composer/autoload_namespaces.php13
-rw-r--r--vendor/composer/autoload_psr4.php10
-rw-r--r--vendor/composer/autoload_real.php50
-rw-r--r--vendor/composer/installed.json394
-rw-r--r--vendor/cssjanus/cssjanus/APACHE-LICENSE-2.0.txt202
-rw-r--r--vendor/cssjanus/cssjanus/README.md45
-rw-r--r--vendor/cssjanus/cssjanus/composer.json24
-rw-r--r--vendor/cssjanus/cssjanus/src/CSSJanus.php454
-rw-r--r--vendor/cssjanus/cssjanus/test/codesniffer/ruleset.xml9
-rw-r--r--vendor/cssjanus/cssjanus/test/phpunit.xml8
-rw-r--r--vendor/cssjanus/cssjanus/test/run7
-rw-r--r--vendor/cssjanus/cssjanus/test/suites/CSSJanusTest.php71
-rw-r--r--vendor/leafo/lessphp/LICENSE660
-rw-r--r--vendor/leafo/lessphp/Makefile7
-rw-r--r--vendor/leafo/lessphp/README.md96
-rw-r--r--vendor/leafo/lessphp/composer.json25
-rw-r--r--vendor/leafo/lessphp/docs/docs.md1400
-rw-r--r--vendor/leafo/lessphp/lessc.inc.php3768
-rw-r--r--vendor/leafo/lessphp/lessify23
-rw-r--r--vendor/leafo/lessphp/lessify.inc.php447
-rw-r--r--vendor/leafo/lessphp/package.sh35
-rw-r--r--vendor/leafo/lessphp/plessc250
-rw-r--r--vendor/leafo/lessphp/tests/ApiTest.php196
-rw-r--r--vendor/leafo/lessphp/tests/ErrorHandlingTest.php81
-rw-r--r--vendor/leafo/lessphp/tests/InputTest.php89
-rw-r--r--vendor/leafo/lessphp/tests/README.md24
-rw-r--r--vendor/leafo/lessphp/tests/bootstrap.sh38
-rw-r--r--vendor/leafo/lessphp/tests/inputs/accessors.less.disable36
-rw-r--r--vendor/leafo/lessphp/tests/inputs/arity.less77
-rw-r--r--vendor/leafo/lessphp/tests/inputs/attributes.less41
-rw-r--r--vendor/leafo/lessphp/tests/inputs/builtins.less96
-rw-r--r--vendor/leafo/lessphp/tests/inputs/colors.less154
-rw-r--r--vendor/leafo/lessphp/tests/inputs/compile_on_mixin.less39
-rw-r--r--vendor/leafo/lessphp/tests/inputs/data-uri.less7
-rw-r--r--vendor/leafo/lessphp/tests/inputs/directives.less28
-rw-r--r--vendor/leafo/lessphp/tests/inputs/escape.less18
-rw-r--r--vendor/leafo/lessphp/tests/inputs/font_family.less28
-rw-r--r--vendor/leafo/lessphp/tests/inputs/guards.less74
-rw-r--r--vendor/leafo/lessphp/tests/inputs/hacks.less6
-rw-r--r--vendor/leafo/lessphp/tests/inputs/hi.less5
-rw-r--r--vendor/leafo/lessphp/tests/inputs/ie.less12
-rw-r--r--vendor/leafo/lessphp/tests/inputs/import.less56
-rw-r--r--vendor/leafo/lessphp/tests/inputs/interpolation.less47
-rw-r--r--vendor/leafo/lessphp/tests/inputs/keyframes.less52
-rw-r--r--vendor/leafo/lessphp/tests/inputs/math.less122
-rw-r--r--vendor/leafo/lessphp/tests/inputs/media.less68
-rw-r--r--vendor/leafo/lessphp/tests/inputs/misc.less100
-rw-r--r--vendor/leafo/lessphp/tests/inputs/mixin_functions.less33
-rw-r--r--vendor/leafo/lessphp/tests/inputs/mixin_merging.less.disable100
-rw-r--r--vendor/leafo/lessphp/tests/inputs/mixins.less197
-rw-r--r--vendor/leafo/lessphp/tests/inputs/nested.less60
-rw-r--r--vendor/leafo/lessphp/tests/inputs/pattern_matching.less157
-rw-r--r--vendor/leafo/lessphp/tests/inputs/scopes.less40
-rw-r--r--vendor/leafo/lessphp/tests/inputs/selector_expressions.less29
-rw-r--r--vendor/leafo/lessphp/tests/inputs/site_demos.less120
-rw-r--r--vendor/leafo/lessphp/tests/inputs/test-imports/a.less6
-rw-r--r--vendor/leafo/lessphp/tests/inputs/test-imports/b.less12
-rw-r--r--vendor/leafo/lessphp/tests/inputs/test-imports/file1.less16
-rw-r--r--vendor/leafo/lessphp/tests/inputs/test-imports/file2.less6
-rw-r--r--vendor/leafo/lessphp/tests/inputs/test-imports/file3.less7
-rw-r--r--vendor/leafo/lessphp/tests/inputs/test-imports/inner/file1.less6
-rw-r--r--vendor/leafo/lessphp/tests/inputs/test-imports/inner/file2.less4
-rw-r--r--vendor/leafo/lessphp/tests/inputs/variables.less44
-rw-r--r--vendor/leafo/lessphp/tests/inputs_lessjs/mixins-args.less205
-rw-r--r--vendor/leafo/lessphp/tests/inputs_lessjs/mixins-named-args.less36
-rw-r--r--vendor/leafo/lessphp/tests/inputs_lessjs/strings.less51
-rw-r--r--vendor/leafo/lessphp/tests/outputs/accessors.css14
-rw-r--r--vendor/leafo/lessphp/tests/outputs/arity.css25
-rw-r--r--vendor/leafo/lessphp/tests/outputs/attributes.css105
-rw-r--r--vendor/leafo/lessphp/tests/outputs/builtins.css61
-rw-r--r--vendor/leafo/lessphp/tests/outputs/colors.css103
-rw-r--r--vendor/leafo/lessphp/tests/outputs/compile_on_mixin.css29
-rw-r--r--vendor/leafo/lessphp/tests/outputs/data-uri.css6
-rw-r--r--vendor/leafo/lessphp/tests/outputs/directives.css27
-rw-r--r--vendor/leafo/lessphp/tests/outputs/escape.css14
-rw-r--r--vendor/leafo/lessphp/tests/outputs/font_family.css17
-rw-r--r--vendor/leafo/lessphp/tests/outputs/guards.css27
-rw-r--r--vendor/leafo/lessphp/tests/outputs/hacks.css4
-rw-r--r--vendor/leafo/lessphp/tests/outputs/hi.css3
-rw-r--r--vendor/leafo/lessphp/tests/outputs/ie.css9
-rw-r--r--vendor/leafo/lessphp/tests/outputs/import.css51
-rw-r--r--vendor/leafo/lessphp/tests/outputs/interpolation.css28
-rw-r--r--vendor/leafo/lessphp/tests/outputs/keyframes.css48
-rw-r--r--vendor/leafo/lessphp/tests/outputs/math.css69
-rw-r--r--vendor/leafo/lessphp/tests/outputs/media.css70
-rw-r--r--vendor/leafo/lessphp/tests/outputs/misc.css68
-rw-r--r--vendor/leafo/lessphp/tests/outputs/mixin_functions.css7
-rw-r--r--vendor/leafo/lessphp/tests/outputs/mixin_merging.css42
-rw-r--r--vendor/leafo/lessphp/tests/outputs/mixins.css92
-rw-r--r--vendor/leafo/lessphp/tests/outputs/nested.css51
-rw-r--r--vendor/leafo/lessphp/tests/outputs/nesting.css6
-rw-r--r--vendor/leafo/lessphp/tests/outputs/pattern_matching.css72
-rw-r--r--vendor/leafo/lessphp/tests/outputs/scopes.css11
-rw-r--r--vendor/leafo/lessphp/tests/outputs/selector_expressions.css25
-rw-r--r--vendor/leafo/lessphp/tests/outputs/site_demos.css76
-rw-r--r--vendor/leafo/lessphp/tests/outputs/variables.css19
-rw-r--r--vendor/leafo/lessphp/tests/outputs_lessjs/mixins-args.css113
-rw-r--r--vendor/leafo/lessphp/tests/outputs_lessjs/mixins-named-args.css27
-rw-r--r--vendor/leafo/lessphp/tests/outputs_lessjs/strings.css40
-rw-r--r--vendor/leafo/lessphp/tests/sort.php57
-rw-r--r--vendor/liuggio/statsd-php-client/LICENSE19
-rw-r--r--vendor/liuggio/statsd-php-client/Readme.md149
-rw-r--r--vendor/liuggio/statsd-php-client/composer.json26
-rw-r--r--vendor/liuggio/statsd-php-client/phpunit.xml.dist15
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdData.php78
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdDataInterface.php41
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Exception/InvalidArgumentException.php7
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactory.php130
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactoryInterface.php99
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatter.php101
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Handler/StatsDHandler.php120
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/EchoSender.php35
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SenderInterface.php32
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SocketSender.php88
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SysLogSender.php42
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClient.php211
-rw-r--r--vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClientInterface.php23
-rw-r--r--vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Entity/StatsdDataTest.php27
-rw-r--r--vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatterTest.php169
-rw-r--r--vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Handler/StatsDHandlerTest.php89
-rw-r--r--vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/ReadmeTest.php72
-rw-r--r--vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdClientTest.php228
-rw-r--r--vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdDataFactoryTest.php97
-rw-r--r--vendor/liuggio/statsd-php-client/tests/bootstrap.php5
-rw-r--r--vendor/oojs/oojs-ui/.csscomb.json24
-rw-r--r--vendor/oojs/oojs-ui/.csslintrc11
-rw-r--r--vendor/oojs/oojs-ui/.mailmap18
-rw-r--r--vendor/oojs/oojs-ui/.npmignore2
-rw-r--r--vendor/oojs/oojs-ui/AUTHORS.txt34
-rw-r--r--vendor/oojs/oojs-ui/Doxyfile33
-rw-r--r--vendor/oojs/oojs-ui/Gemfile.lock25
-rw-r--r--vendor/oojs/oojs-ui/Gruntfile.js401
-rw-r--r--vendor/oojs/oojs-ui/History.md668
-rw-r--r--vendor/oojs/oojs-ui/LICENSE-MIT20
-rw-r--r--vendor/oojs/oojs-ui/README.md79
-rw-r--r--vendor/oojs/oojs-ui/bin/doccomparer.rb165
-rw-r--r--vendor/oojs/oojs-ui/bin/docparser.rb243
-rw-r--r--vendor/oojs/oojs-ui/bin/generate-JSPHP-for-karma.php50
-rw-r--r--vendor/oojs/oojs-ui/bin/testsuitegenerator.rb146
-rw-r--r--vendor/oojs/oojs-ui/build/banner.txt10
-rw-r--r--vendor/oojs/oojs-ui/build/modules.json240
-rw-r--r--vendor/oojs/oojs-ui/build/tasks/colorize-svg.js538
-rw-r--r--vendor/oojs/oojs-ui/build/tasks/typos.js89
-rw-r--r--vendor/oojs/oojs-ui/build/typos.json17
-rw-r--r--vendor/oojs/oojs-ui/composer.json24
-rw-r--r--vendor/oojs/oojs-ui/demos/demo.js504
-rw-r--r--vendor/oojs/oojs-ui/demos/index.html39
-rw-r--r--vendor/oojs/oojs-ui/demos/infusion.js66
-rw-r--r--vendor/oojs/oojs-ui/demos/pages/dialogs.js602
-rw-r--r--vendor/oojs/oojs-ui/demos/pages/icons.js295
-rw-r--r--vendor/oojs/oojs-ui/demos/pages/toolbars.js338
-rw-r--r--vendor/oojs/oojs-ui/demos/pages/widgets.js1428
-rw-r--r--vendor/oojs/oojs-ui/demos/styles/demo.css250
-rw-r--r--vendor/oojs/oojs-ui/demos/widgets.php830
-rw-r--r--vendor/oojs/oojs-ui/i18n/ace.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/af.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/am.json7
-rw-r--r--vendor/oojs/oojs-ui/i18n/ar.json28
-rw-r--r--vendor/oojs/oojs-ui/i18n/arc.json7
-rw-r--r--vendor/oojs/oojs-ui/i18n/arq.json16
-rw-r--r--vendor/oojs/oojs-ui/i18n/ast.json18
-rw-r--r--vendor/oojs/oojs-ui/i18n/awa.json8
-rw-r--r--vendor/oojs/oojs-ui/i18n/az.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/ba.json14
-rw-r--r--vendor/oojs/oojs-ui/i18n/bcc.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/bcl.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/be-tarask.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/be.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/bg.json12
-rw-r--r--vendor/oojs/oojs-ui/i18n/bn.json26
-rw-r--r--vendor/oojs/oojs-ui/i18n/br.json22
-rw-r--r--vendor/oojs/oojs-ui/i18n/bs.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/ca.json28
-rw-r--r--vendor/oojs/oojs-ui/i18n/ce.json17
-rw-r--r--vendor/oojs/oojs-ui/i18n/ckb.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/co.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/crh-cyrl.json8
-rw-r--r--vendor/oojs/oojs-ui/i18n/crh-latn.json8
-rw-r--r--vendor/oojs/oojs-ui/i18n/cs.json29
-rw-r--r--vendor/oojs/oojs-ui/i18n/cu.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/cy.json14
-rw-r--r--vendor/oojs/oojs-ui/i18n/da.json16
-rw-r--r--vendor/oojs/oojs-ui/i18n/de.json28
-rw-r--r--vendor/oojs/oojs-ui/i18n/diq.json16
-rw-r--r--vendor/oojs/oojs-ui/i18n/dsb.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/egl.json14
-rw-r--r--vendor/oojs/oojs-ui/i18n/el.json26
-rw-r--r--vendor/oojs/oojs-ui/i18n/eml.json14
-rw-r--r--vendor/oojs/oojs-ui/i18n/en.json31
-rw-r--r--vendor/oojs/oojs-ui/i18n/eo.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/es.json33
-rw-r--r--vendor/oojs/oojs-ui/i18n/et.json20
-rw-r--r--vendor/oojs/oojs-ui/i18n/eu.json20
-rw-r--r--vendor/oojs/oojs-ui/i18n/fa.json29
-rw-r--r--vendor/oojs/oojs-ui/i18n/fi.json31
-rw-r--r--vendor/oojs/oojs-ui/i18n/fo.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/fr.json45
-rw-r--r--vendor/oojs/oojs-ui/i18n/frr.json12
-rw-r--r--vendor/oojs/oojs-ui/i18n/fur.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/fy.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/gd.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/gl.json22
-rw-r--r--vendor/oojs/oojs-ui/i18n/gu.json17
-rw-r--r--vendor/oojs/oojs-ui/i18n/he.json30
-rw-r--r--vendor/oojs/oojs-ui/i18n/hi.json24
-rw-r--r--vendor/oojs/oojs-ui/i18n/hr.json21
-rw-r--r--vendor/oojs/oojs-ui/i18n/hsb.json20
-rw-r--r--vendor/oojs/oojs-ui/i18n/hu.json23
-rw-r--r--vendor/oojs/oojs-ui/i18n/hy.json21
-rw-r--r--vendor/oojs/oojs-ui/i18n/ia.json16
-rw-r--r--vendor/oojs/oojs-ui/i18n/id.json26
-rw-r--r--vendor/oojs/oojs-ui/i18n/ie.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/ilo.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/is.json20
-rw-r--r--vendor/oojs/oojs-ui/i18n/it.json30
-rw-r--r--vendor/oojs/oojs-ui/i18n/ja.json24
-rw-r--r--vendor/oojs/oojs-ui/i18n/jv.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/ka.json26
-rw-r--r--vendor/oojs/oojs-ui/i18n/kk-cyrl.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/km.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/kn.json22
-rw-r--r--vendor/oojs/oojs-ui/i18n/ko.json25
-rw-r--r--vendor/oojs/oojs-ui/i18n/krc.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/ksh.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/ku-latn.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/kw.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/ky.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/lb.json25
-rw-r--r--vendor/oojs/oojs-ui/i18n/lmo.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/lt.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/lv.json21
-rw-r--r--vendor/oojs/oojs-ui/i18n/lzh.json8
-rw-r--r--vendor/oojs/oojs-ui/i18n/mg.json7
-rw-r--r--vendor/oojs/oojs-ui/i18n/min.json14
-rw-r--r--vendor/oojs/oojs-ui/i18n/mk.json21
-rw-r--r--vendor/oojs/oojs-ui/i18n/ml.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/mr.json15
-rw-r--r--vendor/oojs/oojs-ui/i18n/ms.json17
-rw-r--r--vendor/oojs/oojs-ui/i18n/nap.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/nb.json25
-rw-r--r--vendor/oojs/oojs-ui/i18n/nds-nl.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/nds.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/ne.json20
-rw-r--r--vendor/oojs/oojs-ui/i18n/nl.json35
-rw-r--r--vendor/oojs/oojs-ui/i18n/nn.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/oc.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/om.json20
-rw-r--r--vendor/oojs/oojs-ui/i18n/or.json21
-rw-r--r--vendor/oojs/oojs-ui/i18n/pa.json22
-rw-r--r--vendor/oojs/oojs-ui/i18n/pfl.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/pl.json34
-rw-r--r--vendor/oojs/oojs-ui/i18n/pms.json12
-rw-r--r--vendor/oojs/oojs-ui/i18n/ps.json18
-rw-r--r--vendor/oojs/oojs-ui/i18n/pt-br.json18
-rw-r--r--vendor/oojs/oojs-ui/i18n/pt.json28
-rw-r--r--vendor/oojs/oojs-ui/i18n/qqq.json35
-rw-r--r--vendor/oojs/oojs-ui/i18n/qu.json12
-rw-r--r--vendor/oojs/oojs-ui/i18n/ro.json23
-rw-r--r--vendor/oojs/oojs-ui/i18n/roa-tara.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/ru.json34
-rw-r--r--vendor/oojs/oojs-ui/i18n/sah.json8
-rw-r--r--vendor/oojs/oojs-ui/i18n/scn.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/sco.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/sh.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/si.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/sk.json12
-rw-r--r--vendor/oojs/oojs-ui/i18n/sl.json22
-rw-r--r--vendor/oojs/oojs-ui/i18n/sq.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/sr-ec.json21
-rw-r--r--vendor/oojs/oojs-ui/i18n/sr-el.json19
-rw-r--r--vendor/oojs/oojs-ui/i18n/sv.json29
-rw-r--r--vendor/oojs/oojs-ui/i18n/sw.json15
-rw-r--r--vendor/oojs/oojs-ui/i18n/ta.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/te.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/tg-cyrl.json11
-rw-r--r--vendor/oojs/oojs-ui/i18n/th.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/tl.json14
-rw-r--r--vendor/oojs/oojs-ui/i18n/tr.json27
-rw-r--r--vendor/oojs/oojs-ui/i18n/tt-cyrl.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/ug-arab.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/uk.json33
-rw-r--r--vendor/oojs/oojs-ui/i18n/uz.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/vec.json13
-rw-r--r--vendor/oojs/oojs-ui/i18n/vi.json22
-rw-r--r--vendor/oojs/oojs-ui/i18n/vo.json8
-rw-r--r--vendor/oojs/oojs-ui/i18n/wuu.json9
-rw-r--r--vendor/oojs/oojs-ui/i18n/yi.json18
-rw-r--r--vendor/oojs/oojs-ui/i18n/yo.json10
-rw-r--r--vendor/oojs/oojs-ui/i18n/yue.json16
-rw-r--r--vendor/oojs/oojs-ui/i18n/zh-hans.json34
-rw-r--r--vendor/oojs/oojs-ui/i18n/zh-hant.json32
-rw-r--r--vendor/oojs/oojs-ui/jsduck.categories.json80
-rw-r--r--vendor/oojs/oojs-ui/jsduck.eg-iframe.html33
-rw-r--r--vendor/oojs/oojs-ui/jsduck.external.js26
-rw-r--r--vendor/oojs/oojs-ui/jsduck.json16
-rw-r--r--vendor/oojs/oojs-ui/php/Element.php297
-rw-r--r--vendor/oojs/oojs-ui/php/ElementMixin.php55
-rw-r--r--vendor/oojs/oojs-ui/php/Exception.php6
-rw-r--r--vendor/oojs/oojs-ui/php/HtmlSnippet.php36
-rw-r--r--vendor/oojs/oojs-ui/php/Layout.php21
-rw-r--r--vendor/oojs/oojs-ui/php/Tag.php365
-rw-r--r--vendor/oojs/oojs-ui/php/Theme.php58
-rw-r--r--vendor/oojs/oojs-ui/php/Widget.php71
-rw-r--r--vendor/oojs/oojs-ui/php/elements/ButtonElement.php102
-rw-r--r--vendor/oojs/oojs-ui/php/elements/FlaggedElement.php133
-rw-r--r--vendor/oojs/oojs-ui/php/elements/GroupElement.php129
-rw-r--r--vendor/oojs/oojs-ui/php/elements/IconElement.php76
-rw-r--r--vendor/oojs/oojs-ui/php/elements/IndicatorElement.php78
-rw-r--r--vendor/oojs/oojs-ui/php/elements/LabelElement.php77
-rw-r--r--vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php88
-rw-r--r--vendor/oojs/oojs-ui/php/elements/TitledElement.php74
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/FieldLayout.php140
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php32
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/FormLayout.php47
-rw-r--r--vendor/oojs/oojs-ui/php/layouts/PanelLayout.php61
-rw-r--r--vendor/oojs/oojs-ui/php/themes/ApexTheme.php6
-rw-r--r--vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php39
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php28
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php110
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php166
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php70
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php99
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/IconWidget.php34
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php32
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/InputWidget.php144
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/LabelWidget.php48
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php60
-rw-r--r--vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php151
-rw-r--r--vendor/oojs/oojs-ui/src/ActionSet.js504
-rw-r--r--vendor/oojs/oojs-ui/src/Dialog.js323
-rw-r--r--vendor/oojs/oojs-ui/src/Element.js748
-rw-r--r--vendor/oojs/oojs-ui/src/Error.js91
-rw-r--r--vendor/oojs/oojs-ui/src/HtmlSnippet.js29
-rw-r--r--vendor/oojs/oojs-ui/src/Layout.js33
-rw-r--r--vendor/oojs/oojs-ui/src/Process.js166
-rw-r--r--vendor/oojs/oojs-ui/src/Theme.js48
-rw-r--r--vendor/oojs/oojs-ui/src/Tool.js279
-rw-r--r--vendor/oojs/oojs-ui/src/ToolFactory.js120
-rw-r--r--vendor/oojs/oojs-ui/src/ToolGroup.js338
-rw-r--r--vendor/oojs/oojs-ui/src/ToolGroupFactory.js38
-rw-r--r--vendor/oojs/oojs-ui/src/Toolbar.js481
-rw-r--r--vendor/oojs/oojs-ui/src/Widget.js102
-rw-r--r--vendor/oojs/oojs-ui/src/Window.js628
-rw-r--r--vendor/oojs/oojs-ui/src/WindowManager.js675
-rw-r--r--vendor/oojs/oojs-ui/src/core.js286
-rw-r--r--vendor/oojs/oojs-ui/src/dialogs/MessageDialog.js325
-rw-r--r--vendor/oojs/oojs-ui/src/dialogs/ProcessDialog.js296
-rw-r--r--vendor/oojs/oojs-ui/src/elements/ButtonElement.js263
-rw-r--r--vendor/oojs/oojs-ui/src/elements/ClippableElement.js205
-rw-r--r--vendor/oojs/oojs-ui/src/elements/DraggableElement.js142
-rw-r--r--vendor/oojs/oojs-ui/src/elements/DraggableGroupElement.js261
-rw-r--r--vendor/oojs/oojs-ui/src/elements/FlaggedElement.js209
-rw-r--r--vendor/oojs/oojs-ui/src/elements/GroupElement.js290
-rw-r--r--vendor/oojs/oojs-ui/src/elements/IconElement.js187
-rw-r--r--vendor/oojs/oojs-ui/src/elements/IndicatorElement.js168
-rw-r--r--vendor/oojs/oojs-ui/src/elements/LabelElement.js152
-rw-r--r--vendor/oojs/oojs-ui/src/elements/LookupElement.js352
-rw-r--r--vendor/oojs/oojs-ui/src/elements/PendingElement.js84
-rw-r--r--vendor/oojs/oojs-ui/src/elements/PopupElement.js36
-rw-r--r--vendor/oojs/oojs-ui/src/elements/TabIndexedElement.js138
-rw-r--r--vendor/oojs/oojs-ui/src/elements/TitledElement.js106
-rw-r--r--vendor/oojs/oojs-ui/src/intro.js.txt3
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/ActionFieldLayout.js81
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/BookletLayout.js542
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/CardLayout.js138
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/FieldLayout.js160
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/FieldsetLayout.js86
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/FormLayout.js119
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/IndexLayout.js452
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/MenuLayout.js156
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/PageLayout.js138
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/PanelLayout.js55
-rw-r--r--vendor/oojs/oojs-ui/src/layouts/StackLayout.js214
-rw-r--r--vendor/oojs/oojs-ui/src/outro.js.txt1
-rw-r--r--vendor/oojs/oojs-ui/src/styles/Dialog.less35
-rw-r--r--vendor/oojs/oojs-ui/src/styles/Element.less9
-rw-r--r--vendor/oojs/oojs-ui/src/styles/Layout.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/Tool.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/ToolGroup.less21
-rw-r--r--vendor/oojs/oojs-ui/src/styles/Toolbar.less52
-rw-r--r--vendor/oojs/oojs-ui/src/styles/Widget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/Window.less32
-rw-r--r--vendor/oojs/oojs-ui/src/styles/WindowManager.less39
-rw-r--r--vendor/oojs/oojs-ui/src/styles/common.less102
-rw-r--r--vendor/oojs/oojs-ui/src/styles/core.less114
-rw-r--r--vendor/oojs/oojs-ui/src/styles/dialogs/MessageDialog.less45
-rw-r--r--vendor/oojs/oojs-ui/src/styles/dialogs/ProcessDialog.less52
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/ButtonElement.less62
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/ClippableElement.less7
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/DraggableElement.less22
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/DraggableGroupElement.less11
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/FlaggedElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/GroupElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/IconElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/IndicatorElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/LabelElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/LookupElement.less10
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/PopupElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/TabIndexedElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/elements/TitledElement.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/images/grab.curbin0 -> 326 bytes
-rw-r--r--vendor/oojs/oojs-ui/src/styles/images/grabbing.curbin0 -> 326 bytes
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/ActionFieldLayout.less26
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/BookletLayout.less43
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/CardLayout.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/FieldLayout.less59
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/FieldsetLayout.less31
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/FormLayout.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/IndexLayout.less13
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/MenuLayout.less108
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/PageLayout.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/PanelLayout.less19
-rw-r--r--vendor/oojs/oojs-ui/src/styles/layouts/StackLayout.less10
-rw-r--r--vendor/oojs/oojs-ui/src/styles/theme.less91
-rw-r--r--vendor/oojs/oojs-ui/src/styles/toolgroups/BarToolGroup.less51
-rw-r--r--vendor/oojs/oojs-ui/src/styles/toolgroups/ListToolGroup.less21
-rw-r--r--vendor/oojs/oojs-ui/src/styles/toolgroups/MenuToolGroup.less19
-rw-r--r--vendor/oojs/oojs-ui/src/styles/toolgroups/PopupToolGroup.less73
-rw-r--r--vendor/oojs/oojs-ui/src/styles/tools/PopupTool.less12
-rw-r--r--vendor/oojs/oojs-ui/src/styles/tools/ToolGroupTool.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ActionWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ButtonGroupWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ButtonInputWidget.less8
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ButtonOptionWidget.less18
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ButtonSelectWidget.less8
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ButtonWidget.less8
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/CheckboxInputWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ComboBoxWidget.less13
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/DecoratedOptionWidget.less12
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/DropdownInputWidget.less17
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/DropdownWidget.less33
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/IconWidget.less10
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/IndicatorWidget.less10
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/InputWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/LabelWidget.less7
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/MenuOptionWidget.less21
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/MenuSectionOptionWidget.less8
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/MenuSelectWidget.less15
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/OptionWidget.less20
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/OutlineControlsWidget.less32
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/OutlineOptionWidget.less9
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/OutlineSelectWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/PopupButtonWidget.less12
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/PopupWidget.less49
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ProgressBarWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/RadioInputWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/RadioOptionWidget.less13
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/RadioSelectWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/SearchWidget.less25
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/SelectWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/TabOptionWidget.less8
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/TabSelectWidget.less9
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/TextInputMenuSelectWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/TextInputWidget.less71
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ToggleButtonWidget.less8
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ToggleSwitchWidget.less41
-rw-r--r--vendor/oojs/oojs-ui/src/styles/widgets/ToggleWidget.less5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/ApexTheme.js18
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/common.less27
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/core.less12
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/elements.less247
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/icons-editing-advanced.json75
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/icons-editing-core.json24
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/icons-editing-list.json22
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/icons-editing-styling.json72
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/icons-moderation.json33
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/icons-movement.json27
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/icons.json50
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/add.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/advanced.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/alert.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-center.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-left.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-right.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/block.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-a.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-ain.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-dad.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-armn-to.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-b.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-be.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-te.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-zhe.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-f.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-g.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-geor-man.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-l.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-n.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-v.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/cancel.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretDown.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretUp.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/case-sensitive.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/check.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/circle.svg2
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/close.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/code.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/collapse.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/comment.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/downTriangle.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-ltr.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-rtl.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-ltr.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-rtl.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/ellipsis.svg14
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/expand.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-ltr.svg16
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-rtl.svg16
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-ltr.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-rtl.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/history.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-ltr.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-rtl.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/info.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/insert.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-a.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-keheh-jeem.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-meem.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-armn-sha.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-c.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-d.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-e.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-geor-kan.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-i.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-k.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-s.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/language.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/link.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-ltr.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-rtl.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-ltr.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-rtl.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/lock.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/menu.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/move.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-ltr.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-rtl.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/picture.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-ltr.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-rtl.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/regular-expression.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/remove.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/search.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/secure-link.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/settings.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/specialCharacter.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/star.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-a.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-s.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-y.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-ltr.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-rtl.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-ltr.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-rtl.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-caption.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-ltr.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-rtl.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-after.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-before.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-merge-cells.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/table.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/tag.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-lefttoright.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-righttoleft.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-style.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/trash.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/unStar.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-a.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-u.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/upTriangle.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/wikiText.svg15
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/icons/window.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/alert.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-down.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-up.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/required.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/textures/pending.gifbin0 -> 2032 bytes
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/textures/transparency.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/images/toolbar-shadow.pngbin0 -> 131 bytes
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/indicators.json22
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/layouts.less135
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/textures.json8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/tools.less422
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/widgets.less797
-rw-r--r--vendor/oojs/oojs-ui/src/themes/apex/windows.less307
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/BlankTheme.js32
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/common.less10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/core.less12
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/elements.less29
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/images/icons/README.txt1
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/images/indicators/README.txt1
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/images/textures/README.txt1
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/layouts.less26
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/tools.less20
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/widgets.less77
-rw-r--r--vendor/oojs/oojs-ui/src/themes/blank/windows.less11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/MediaWikiTheme.js56
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/common.less69
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/core.less12
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/elements.less319
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-alerts.json33
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-content.json50
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-advanced.json75
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-core.json45
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-list.json22
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-styling.json72
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-interactions.json52
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-layout.json43
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-location.json19
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-media.json27
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-moderation.json52
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-movement.json27
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-user.json19
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons-wikimedia.json9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/icons.json75
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/add.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/advanced.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/alert.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-center.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-left.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-right.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-ltr.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-rtl.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bell.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/beta.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/betaLaunch.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/block.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-a.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-ain.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-dad.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-armn-to.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-b.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-be.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-te.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-zhe.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-f.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-g.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-geor-man.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-l.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-n.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-v.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/cancel.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretDown.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretUp.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/case-sensitive.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/check.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/circle.svg2
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clear.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clock.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/code.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/collapse.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/comment.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/downTriangle.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-ltr.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-rtl.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-ltr.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-rtl.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ellipsis.svg14
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/expand.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eye.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eyeClosed.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-ltr.svg16
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-rtl.svg16
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/heart.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-ltr.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-rtl.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/history.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/info.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/insert.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-a.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-keheh-jeem.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-meem.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-armn-sha.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-c.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-d.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-e.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-geor-kan.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-i.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-k.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-s.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/language.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-ltr.svg13
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-cc.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikimediaCommons.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikipedia.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPin.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-ltr.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-rtl.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/menu.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/picture.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-ltr.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-rtl.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-ltr.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-rtl.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/regular-expression.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/remove.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ribbonPrize.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/secure-link.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/settings.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/specialCharacter.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/star.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stop.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-a.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-s.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-y.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSideMenu.svg12
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-ltr.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-rtl.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-ltr.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-rtl.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-ltr.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-rtl.svg5
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-caption.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-ltr.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-rtl.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-after.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-before.svg11
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-merge-cells.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/tag.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-ltr.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-rtl.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-lefttoright.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-righttoleft.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-style.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trash.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unStar.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-a.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-u.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upTriangle.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userAvatar.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-ltr.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-rtl.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewCompact.svg14
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-ltr.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-rtl.svg9
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/visionSimulator.svg4
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikiText.svg15
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-ltr.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-rtl.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/window.svg7
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/alert.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-down.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-ltr.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-rtl.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-up.svg8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/required.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-ltr.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-rtl.svg6
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/pending.gifbin0 -> 2032 bytes
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/transparency.svg10
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/indicators.json29
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/layouts.less135
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/textures.json8
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/tools.less422
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/widgets.less978
-rw-r--r--vendor/oojs/oojs-ui/src/themes/mediawiki/windows.less300
-rw-r--r--vendor/oojs/oojs-ui/src/toolgroups/BarToolGroup.js35
-rw-r--r--vendor/oojs/oojs-ui/src/toolgroups/ListToolGroup.js133
-rw-r--r--vendor/oojs/oojs-ui/src/toolgroups/MenuToolGroup.js58
-rw-r--r--vendor/oojs/oojs-ui/src/toolgroups/PopupToolGroup.js187
-rw-r--r--vendor/oojs/oojs-ui/src/tools/PopupTool.js59
-rw-r--r--vendor/oojs/oojs-ui/src/tools/ToolGroupTool.js96
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ActionWidget.js170
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ButtonGroupWidget.js53
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ButtonInputWidget.js121
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ButtonOptionWidget.js60
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ButtonSelectWidget.js62
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ButtonWidget.js215
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/CheckboxInputWidget.js110
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ComboBoxWidget.js230
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/DecoratedOptionWidget.js55
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/DropdownInputWidget.js137
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/DropdownWidget.js149
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/GroupWidget.js48
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/IconWidget.js54
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/IndicatorWidget.js52
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/InputWidget.js207
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ItemWidget.js48
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/LabelWidget.js84
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/MenuOptionWidget.js33
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/MenuSectionOptionWidget.js55
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/MenuSelectWidget.js254
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/OptionWidget.js177
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/OutlineControlsWidget.js145
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/OutlineOptionWidget.js130
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/OutlineSelectWidget.js34
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/PopupButtonWidget.js57
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/PopupWidget.js395
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ProgressBarWidget.js96
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/RadioInputWidget.js94
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/RadioOptionWidget.js66
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/RadioSelectWidget.js58
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/SearchWidget.js176
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/SelectWidget.js630
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/TabOptionWidget.js31
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/TabSelectWidget.js33
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/TextInputMenuSelectWidget.js103
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js535
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ToggleButtonWidget.js114
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ToggleSwitchWidget.js92
-rw-r--r--vendor/oojs/oojs-ui/src/widgets/ToggleWidget.js71
-rw-r--r--vendor/oojs/oojs-ui/tests/Element.test.js52
-rw-r--r--vendor/oojs/oojs-ui/tests/JSPHP.test.karma.js61
-rw-r--r--vendor/oojs/oojs-ui/tests/JSPHP.test.standalone.js55
-rw-r--r--vendor/oojs/oojs-ui/tests/Process.test.js179
-rw-r--r--vendor/oojs/oojs-ui/tests/QUnit.assert.equalDomElement.js113
-rw-r--r--vendor/oojs/oojs-ui/tests/elements/FlaggedElement.test.js64
-rw-r--r--vendor/oojs/oojs-ui/tests/index.php77
-rw-r--r--vendor/psr/log/LICENSE19
-rw-r--r--vendor/psr/log/Psr/Log/AbstractLogger.php120
-rw-r--r--vendor/psr/log/Psr/Log/InvalidArgumentException.php7
-rw-r--r--vendor/psr/log/Psr/Log/LogLevel.php18
-rw-r--r--vendor/psr/log/Psr/Log/LoggerAwareInterface.php17
-rw-r--r--vendor/psr/log/Psr/Log/LoggerAwareTrait.php22
-rw-r--r--vendor/psr/log/Psr/Log/LoggerInterface.php114
-rw-r--r--vendor/psr/log/Psr/Log/LoggerTrait.php131
-rw-r--r--vendor/psr/log/Psr/Log/NullLogger.php27
-rw-r--r--vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php116
-rw-r--r--vendor/psr/log/README.md45
-rw-r--r--vendor/psr/log/composer.json17
-rw-r--r--vendor/wikimedia/cdb/COPYING342
-rw-r--r--vendor/wikimedia/cdb/Doxyfile32
-rw-r--r--vendor/wikimedia/cdb/README.md48
-rw-r--r--vendor/wikimedia/cdb/composer.json26
-rw-r--r--vendor/wikimedia/cdb/doc/README1
-rw-r--r--vendor/wikimedia/cdb/src/Exception.php28
-rw-r--r--vendor/wikimedia/cdb/src/Reader.php85
-rw-r--r--vendor/wikimedia/cdb/src/Reader/DBA.php49
-rw-r--r--vendor/wikimedia/cdb/src/Reader/PHP.php220
-rw-r--r--vendor/wikimedia/cdb/src/Util.php100
-rw-r--r--vendor/wikimedia/cdb/src/Writer.php99
-rw-r--r--vendor/wikimedia/cdb/src/Writer/DBA.php63
-rw-r--r--vendor/wikimedia/cdb/src/Writer/PHP.php240
-rw-r--r--vendor/wikimedia/cdb/test/CdbTest.php95
-rw-r--r--vendor/wikimedia/composer-merge-plugin/LICENSE19
-rw-r--r--vendor/wikimedia/composer-merge-plugin/README.md80
-rw-r--r--vendor/wikimedia/composer-merge-plugin/composer.json38
-rw-r--r--vendor/wikimedia/composer-merge-plugin/src/MergePlugin.php411
-rw-r--r--vendor/wikimedia/utfnormal/README.md69
-rw-r--r--vendor/wikimedia/utfnormal/scripts/benchmark.php101
-rw-r--r--vendor/wikimedia/utfnormal/scripts/generate.php273
-rw-r--r--vendor/wikimedia/utfnormal/scripts/memstress.php103
-rw-r--r--vendor/wikimedia/utfnormal/scripts/testdata/berlin.txt454
-rw-r--r--vendor/wikimedia/utfnormal/scripts/testdata/bulgakov.txt40
-rw-r--r--vendor/wikimedia/utfnormal/scripts/testdata/tokyo.txt587
-rw-r--r--vendor/wikimedia/utfnormal/scripts/testdata/washington.txt281
-rw-r--r--vendor/wikimedia/utfnormal/scripts/testdata/young.txt274
-rw-r--r--vendor/wikimedia/utfnormal/src/Constants.php103
-rw-r--r--vendor/wikimedia/utfnormal/src/UtfNormalData.inc14
-rw-r--r--vendor/wikimedia/utfnormal/src/UtfNormalDataK.inc11
-rw-r--r--vendor/wikimedia/utfnormal/src/Util.php157
-rw-r--r--vendor/wikimedia/utfnormal/src/Validator.php770
-rw-r--r--vendor/zordius/lightncandy/.scrutinizer.yml95
-rw-r--r--vendor/zordius/lightncandy/CONTRIBUTING.md18
-rw-r--r--vendor/zordius/lightncandy/HISTORY.md183
-rw-r--r--vendor/zordius/lightncandy/LICENSE.txt19
-rw-r--r--vendor/zordius/lightncandy/README.md670
-rw-r--r--vendor/zordius/lightncandy/UPGRADE.md18
-rw-r--r--vendor/zordius/lightncandy/build/gen_doc6
-rw-r--r--vendor/zordius/lightncandy/build/gen_test.php64
-rw-r--r--vendor/zordius/lightncandy/build/push_ghpage12
-rw-r--r--vendor/zordius/lightncandy/build/runphp2
-rw-r--r--vendor/zordius/lightncandy/build/travis_push60
-rw-r--r--vendor/zordius/lightncandy/composer.json22
-rw-r--r--vendor/zordius/lightncandy/example_debug.pngbin0 -> 2930 bytes
-rw-r--r--vendor/zordius/lightncandy/phpunit.xml23
-rw-r--r--vendor/zordius/lightncandy/src/lightncandy.php2560
-rw-r--r--vendor/zordius/lightncandy/tests/LCRun3Test.php376
-rw-r--r--vendor/zordius/lightncandy/tests/LightnCandyTest.php460
-rw-r--r--vendor/zordius/lightncandy/tests/errorTest.php410
-rw-r--r--vendor/zordius/lightncandy/tests/example_debug.php28
-rw-r--r--vendor/zordius/lightncandy/tests/example_helpers.php58
-rw-r--r--vendor/zordius/lightncandy/tests/handlebarsSpecTest.php162
-rw-r--r--vendor/zordius/lightncandy/tests/helpers_for_test.php137
-rw-r--r--vendor/zordius/lightncandy/tests/mustacheSpecTest.php53
-rw-r--r--vendor/zordius/lightncandy/tests/recursive.tmpl1
-rw-r--r--vendor/zordius/lightncandy/tests/regressionTest.php736
-rw-r--r--vendor/zordius/lightncandy/tests/test1.tmpl1
-rw-r--r--vendor/zordius/lightncandy/tests/test2.tmpl1
-rw-r--r--vendor/zordius/lightncandy/tests/test3.tmpl1
1060 files changed, 65806 insertions, 0 deletions
diff --git a/vendor/autoload.php b/vendor/autoload.php
new file mode 100644
index 00000000..69e72b37
--- /dev/null
+++ b/vendor/autoload.php
@@ -0,0 +1,7 @@
+<?php
+
+// autoload.php @generated by Composer
+
+require_once __DIR__ . '/composer' . '/autoload_real.php';
+
+return ComposerAutoloaderInit9b10cc5cf6896d6e4a31983fc3498786::getLoader();
diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php
new file mode 100644
index 00000000..5e1469e8
--- /dev/null
+++ b/vendor/composer/ClassLoader.php
@@ -0,0 +1,413 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ * Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0 class loader
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-0.md
+ *
+ * $loader = new \Composer\Autoload\ClassLoader();
+ *
+ * // register classes with namespaces
+ * $loader->add('Symfony\Component', __DIR__.'/component');
+ * $loader->add('Symfony', __DIR__.'/framework');
+ *
+ * // activate the autoloader
+ * $loader->register();
+ *
+ * // to enable searching the include path (eg. for PEAR packages)
+ * $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class ClassLoader
+{
+ // PSR-4
+ private $prefixLengthsPsr4 = array();
+ private $prefixDirsPsr4 = array();
+ private $fallbackDirsPsr4 = array();
+
+ // PSR-0
+ private $prefixesPsr0 = array();
+ private $fallbackDirsPsr0 = array();
+
+ private $useIncludePath = false;
+ private $classMap = array();
+
+ private $classMapAuthoritative = false;
+
+ public function getPrefixes()
+ {
+ if (!empty($this->prefixesPsr0)) {
+ return call_user_func_array('array_merge', $this->prefixesPsr0);
+ }
+
+ return array();
+ }
+
+ public function getPrefixesPsr4()
+ {
+ return $this->prefixDirsPsr4;
+ }
+
+ public function getFallbackDirs()
+ {
+ return $this->fallbackDirsPsr0;
+ }
+
+ public function getFallbackDirsPsr4()
+ {
+ return $this->fallbackDirsPsr4;
+ }
+
+ public function getClassMap()
+ {
+ return $this->classMap;
+ }
+
+ /**
+ * @param array $classMap Class to filename map
+ */
+ public function addClassMap(array $classMap)
+ {
+ if ($this->classMap) {
+ $this->classMap = array_merge($this->classMap, $classMap);
+ } else {
+ $this->classMap = $classMap;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix, either
+ * appending or prepending to the ones previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 root directories
+ * @param bool $prepend Whether to prepend the directories
+ */
+ public function add($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ if ($prepend) {
+ $this->fallbackDirsPsr0 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr0
+ );
+ } else {
+ $this->fallbackDirsPsr0 = array_merge(
+ $this->fallbackDirsPsr0,
+ (array) $paths
+ );
+ }
+
+ return;
+ }
+
+ $first = $prefix[0];
+ if (!isset($this->prefixesPsr0[$first][$prefix])) {
+ $this->prefixesPsr0[$first][$prefix] = (array) $paths;
+
+ return;
+ }
+ if ($prepend) {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixesPsr0[$first][$prefix]
+ );
+ } else {
+ $this->prefixesPsr0[$first][$prefix] = array_merge(
+ $this->prefixesPsr0[$first][$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace, either
+ * appending or prepending to the ones previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-0 base directories
+ * @param bool $prepend Whether to prepend the directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function addPsr4($prefix, $paths, $prepend = false)
+ {
+ if (!$prefix) {
+ // Register directories for the root namespace.
+ if ($prepend) {
+ $this->fallbackDirsPsr4 = array_merge(
+ (array) $paths,
+ $this->fallbackDirsPsr4
+ );
+ } else {
+ $this->fallbackDirsPsr4 = array_merge(
+ $this->fallbackDirsPsr4,
+ (array) $paths
+ );
+ }
+ } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+ // Register directories for a new namespace.
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ } elseif ($prepend) {
+ // Prepend directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ (array) $paths,
+ $this->prefixDirsPsr4[$prefix]
+ );
+ } else {
+ // Append directories for an already registered namespace.
+ $this->prefixDirsPsr4[$prefix] = array_merge(
+ $this->prefixDirsPsr4[$prefix],
+ (array) $paths
+ );
+ }
+ }
+
+ /**
+ * Registers a set of PSR-0 directories for a given prefix,
+ * replacing any others previously set for this prefix.
+ *
+ * @param string $prefix The prefix
+ * @param array|string $paths The PSR-0 base directories
+ */
+ public function set($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr0 = (array) $paths;
+ } else {
+ $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Registers a set of PSR-4 directories for a given namespace,
+ * replacing any others previously set for this namespace.
+ *
+ * @param string $prefix The prefix/namespace, with trailing '\\'
+ * @param array|string $paths The PSR-4 base directories
+ *
+ * @throws \InvalidArgumentException
+ */
+ public function setPsr4($prefix, $paths)
+ {
+ if (!$prefix) {
+ $this->fallbackDirsPsr4 = (array) $paths;
+ } else {
+ $length = strlen($prefix);
+ if ('\\' !== $prefix[$length - 1]) {
+ throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+ }
+ $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+ $this->prefixDirsPsr4[$prefix] = (array) $paths;
+ }
+ }
+
+ /**
+ * Turns on searching the include path for class files.
+ *
+ * @param bool $useIncludePath
+ */
+ public function setUseIncludePath($useIncludePath)
+ {
+ $this->useIncludePath = $useIncludePath;
+ }
+
+ /**
+ * Can be used to check if the autoloader uses the include path to check
+ * for classes.
+ *
+ * @return bool
+ */
+ public function getUseIncludePath()
+ {
+ return $this->useIncludePath;
+ }
+
+ /**
+ * Turns off searching the prefix and fallback directories for classes
+ * that have not been registered with the class map.
+ *
+ * @param bool $classMapAuthoritative
+ */
+ public function setClassMapAuthoritative($classMapAuthoritative)
+ {
+ $this->classMapAuthoritative = $classMapAuthoritative;
+ }
+
+ /**
+ * Should class lookup fail if not found in the current class map?
+ *
+ * @return bool
+ */
+ public function isClassMapAuthoritative()
+ {
+ return $this->classMapAuthoritative;
+ }
+
+ /**
+ * Registers this instance as an autoloader.
+ *
+ * @param bool $prepend Whether to prepend the autoloader or not
+ */
+ public function register($prepend = false)
+ {
+ spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+ }
+
+ /**
+ * Unregisters this instance as an autoloader.
+ */
+ public function unregister()
+ {
+ spl_autoload_unregister(array($this, 'loadClass'));
+ }
+
+ /**
+ * Loads the given class or interface.
+ *
+ * @param string $class The name of the class
+ * @return bool|null True if loaded, null otherwise
+ */
+ public function loadClass($class)
+ {
+ if ($file = $this->findFile($class)) {
+ includeFile($file);
+
+ return true;
+ }
+ }
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ *
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731
+ if ('\\' == $class[0]) {
+ $class = substr($class, 1);
+ }
+
+ // class map lookup
+ if (isset($this->classMap[$class])) {
+ return $this->classMap[$class];
+ }
+ if ($this->classMapAuthoritative) {
+ return false;
+ }
+
+ $file = $this->findFileWithExtension($class, '.php');
+
+ // Search for Hack files if we are running on HHVM
+ if ($file === null && defined('HHVM_VERSION')) {
+ $file = $this->findFileWithExtension($class, '.hh');
+ }
+
+ if ($file === null) {
+ // Remember that this class does not exist.
+ return $this->classMap[$class] = false;
+ }
+
+ return $file;
+ }
+
+ private function findFileWithExtension($class, $ext)
+ {
+ // PSR-4 lookup
+ $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+ $first = $class[0];
+ if (isset($this->prefixLengthsPsr4[$first])) {
+ foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($this->prefixDirsPsr4[$prefix] as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-4 fallback dirs
+ foreach ($this->fallbackDirsPsr4 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 lookup
+ if (false !== $pos = strrpos($class, '\\')) {
+ // namespaced class name
+ $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+ . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+ } else {
+ // PEAR-like class name
+ $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+ }
+
+ if (isset($this->prefixesPsr0[$first])) {
+ foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+ if (0 === strpos($class, $prefix)) {
+ foreach ($dirs as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+ }
+ }
+ }
+
+ // PSR-0 fallback dirs
+ foreach ($this->fallbackDirsPsr0 as $dir) {
+ if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+ return $file;
+ }
+ }
+
+ // PSR-0 include paths.
+ if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+ return $file;
+ }
+ }
+}
+
+/**
+ * Scope isolated include.
+ *
+ * Prevents access to $this/self from included files.
+ */
+function includeFile($file)
+{
+ include $file;
+}
diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php
new file mode 100644
index 00000000..79053ea1
--- /dev/null
+++ b/vendor/composer/autoload_classmap.php
@@ -0,0 +1,85 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'CSSJanus' => $vendorDir . '/cssjanus/cssjanus/src/CSSJanus.php',
+ 'CSSJanusTokenizer' => $vendorDir . '/cssjanus/cssjanus/src/CSSJanus.php',
+ 'Cdb\\Exception' => $vendorDir . '/wikimedia/cdb/src/Exception.php',
+ 'Cdb\\Reader' => $vendorDir . '/wikimedia/cdb/src/Reader.php',
+ 'Cdb\\Reader\\DBA' => $vendorDir . '/wikimedia/cdb/src/Reader/DBA.php',
+ 'Cdb\\Reader\\PHP' => $vendorDir . '/wikimedia/cdb/src/Reader/PHP.php',
+ 'Cdb\\Util' => $vendorDir . '/wikimedia/cdb/src/Util.php',
+ 'Cdb\\Writer' => $vendorDir . '/wikimedia/cdb/src/Writer.php',
+ 'Cdb\\Writer\\DBA' => $vendorDir . '/wikimedia/cdb/src/Writer/DBA.php',
+ 'Cdb\\Writer\\PHP' => $vendorDir . '/wikimedia/cdb/src/Writer/PHP.php',
+ 'ComposerHookHandler' => $baseDir . '/includes/composer/ComposerHookHandler.php',
+ 'LCRun3' => $vendorDir . '/zordius/lightncandy/src/lightncandy.php',
+ 'LightnCandy' => $vendorDir . '/zordius/lightncandy/src/lightncandy.php',
+ 'Liuggio\\StatsdClient\\Entity\\StatsdData' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdData.php',
+ 'Liuggio\\StatsdClient\\Entity\\StatsdDataInterface' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdDataInterface.php',
+ 'Liuggio\\StatsdClient\\Exception\\InvalidArgumentException' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Exception/InvalidArgumentException.php',
+ 'Liuggio\\StatsdClient\\Factory\\StatsdDataFactory' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactory.php',
+ 'Liuggio\\StatsdClient\\Factory\\StatsdDataFactoryInterface' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactoryInterface.php',
+ 'Liuggio\\StatsdClient\\Monolog\\Formatter\\StatsDFormatter' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatter.php',
+ 'Liuggio\\StatsdClient\\Monolog\\Handler\\StatsDHandler' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Handler/StatsDHandler.php',
+ 'Liuggio\\StatsdClient\\Sender\\EchoSender' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/EchoSender.php',
+ 'Liuggio\\StatsdClient\\Sender\\SenderInterface' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SenderInterface.php',
+ 'Liuggio\\StatsdClient\\Sender\\SocketSender' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SocketSender.php',
+ 'Liuggio\\StatsdClient\\Sender\\SysLogSender' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SysLogSender.php',
+ 'Liuggio\\StatsdClient\\StatsdClient' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClient.php',
+ 'Liuggio\\StatsdClient\\StatsdClientInterface' => $vendorDir . '/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClientInterface.php',
+ 'OOUI\\ApexTheme' => $vendorDir . '/oojs/oojs-ui/php/themes/ApexTheme.php',
+ 'OOUI\\ButtonElement' => $vendorDir . '/oojs/oojs-ui/php/elements/ButtonElement.php',
+ 'OOUI\\ButtonGroupWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php',
+ 'OOUI\\ButtonInputWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/ButtonInputWidget.php',
+ 'OOUI\\ButtonWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/ButtonWidget.php',
+ 'OOUI\\CheckboxInputWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php',
+ 'OOUI\\DropdownInputWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/DropdownInputWidget.php',
+ 'OOUI\\Element' => $vendorDir . '/oojs/oojs-ui/php/Element.php',
+ 'OOUI\\ElementMixin' => $vendorDir . '/oojs/oojs-ui/php/ElementMixin.php',
+ 'OOUI\\Exception' => $vendorDir . '/oojs/oojs-ui/php/Exception.php',
+ 'OOUI\\FieldLayout' => $vendorDir . '/oojs/oojs-ui/php/layouts/FieldLayout.php',
+ 'OOUI\\FieldsetLayout' => $vendorDir . '/oojs/oojs-ui/php/layouts/FieldsetLayout.php',
+ 'OOUI\\FlaggedElement' => $vendorDir . '/oojs/oojs-ui/php/elements/FlaggedElement.php',
+ 'OOUI\\FormLayout' => $vendorDir . '/oojs/oojs-ui/php/layouts/FormLayout.php',
+ 'OOUI\\GroupElement' => $vendorDir . '/oojs/oojs-ui/php/elements/GroupElement.php',
+ 'OOUI\\HtmlSnippet' => $vendorDir . '/oojs/oojs-ui/php/HtmlSnippet.php',
+ 'OOUI\\IconElement' => $vendorDir . '/oojs/oojs-ui/php/elements/IconElement.php',
+ 'OOUI\\IconWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/IconWidget.php',
+ 'OOUI\\IndicatorElement' => $vendorDir . '/oojs/oojs-ui/php/elements/IndicatorElement.php',
+ 'OOUI\\IndicatorWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/IndicatorWidget.php',
+ 'OOUI\\InputWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/InputWidget.php',
+ 'OOUI\\LabelElement' => $vendorDir . '/oojs/oojs-ui/php/elements/LabelElement.php',
+ 'OOUI\\LabelWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/LabelWidget.php',
+ 'OOUI\\Layout' => $vendorDir . '/oojs/oojs-ui/php/Layout.php',
+ 'OOUI\\MediaWikiTheme' => $vendorDir . '/oojs/oojs-ui/php/themes/MediaWikiTheme.php',
+ 'OOUI\\PanelLayout' => $vendorDir . '/oojs/oojs-ui/php/layouts/PanelLayout.php',
+ 'OOUI\\RadioInputWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/RadioInputWidget.php',
+ 'OOUI\\TabIndexedElement' => $vendorDir . '/oojs/oojs-ui/php/elements/TabIndexedElement.php',
+ 'OOUI\\Tag' => $vendorDir . '/oojs/oojs-ui/php/Tag.php',
+ 'OOUI\\TextInputWidget' => $vendorDir . '/oojs/oojs-ui/php/widgets/TextInputWidget.php',
+ 'OOUI\\Theme' => $vendorDir . '/oojs/oojs-ui/php/Theme.php',
+ 'OOUI\\TitledElement' => $vendorDir . '/oojs/oojs-ui/php/elements/TitledElement.php',
+ 'OOUI\\Widget' => $vendorDir . '/oojs/oojs-ui/php/Widget.php',
+ 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php',
+ 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php',
+ 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php',
+ 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php',
+ 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php',
+ 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php',
+ 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php',
+ 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php',
+ 'UtfNormal\\Constants' => $vendorDir . '/wikimedia/utfnormal/src/Constants.php',
+ 'UtfNormal\\Utils' => $vendorDir . '/wikimedia/utfnormal/src/Util.php',
+ 'UtfNormal\\Validator' => $vendorDir . '/wikimedia/utfnormal/src/Validator.php',
+ 'Wikimedia\\Composer\\MergePlugin' => $vendorDir . '/wikimedia/composer-merge-plugin/src/MergePlugin.php',
+ 'lessc' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
+ 'lessc_formatter_classic' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
+ 'lessc_formatter_compressed' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
+ 'lessc_formatter_lessjs' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
+ 'lessc_parser' => $vendorDir . '/leafo/lessphp/lessc.inc.php',
+);
diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php
new file mode 100644
index 00000000..a0f180fa
--- /dev/null
+++ b/vendor/composer/autoload_namespaces.php
@@ -0,0 +1,13 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Psr\\Log\\' => array($vendorDir . '/psr/log'),
+ 'Liuggio' => array($vendorDir . '/liuggio/statsd-php-client/src'),
+ 'ComposerHookHandler' => array($baseDir . '/includes/composer'),
+ '' => array($vendorDir . '/cssjanus/cssjanus/src'),
+);
diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php
new file mode 100644
index 00000000..74a09133
--- /dev/null
+++ b/vendor/composer/autoload_psr4.php
@@ -0,0 +1,10 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(dirname(__FILE__));
+$baseDir = dirname($vendorDir);
+
+return array(
+ 'Wikimedia\\Composer\\' => array($vendorDir . '/wikimedia/composer-merge-plugin/src'),
+);
diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php
new file mode 100644
index 00000000..3e640297
--- /dev/null
+++ b/vendor/composer/autoload_real.php
@@ -0,0 +1,50 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit9b10cc5cf6896d6e4a31983fc3498786
+{
+ private static $loader;
+
+ public static function loadClassLoader($class)
+ {
+ if ('Composer\Autoload\ClassLoader' === $class) {
+ require __DIR__ . '/ClassLoader.php';
+ }
+ }
+
+ public static function getLoader()
+ {
+ if (null !== self::$loader) {
+ return self::$loader;
+ }
+
+ spl_autoload_register(array('ComposerAutoloaderInit9b10cc5cf6896d6e4a31983fc3498786', 'loadClassLoader'), true, false);
+ self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+ spl_autoload_unregister(array('ComposerAutoloaderInit9b10cc5cf6896d6e4a31983fc3498786', 'loadClassLoader'));
+
+ $map = require __DIR__ . '/autoload_namespaces.php';
+ foreach ($map as $namespace => $path) {
+ $loader->set($namespace, $path);
+ }
+
+ $map = require __DIR__ . '/autoload_psr4.php';
+ foreach ($map as $namespace => $path) {
+ $loader->setPsr4($namespace, $path);
+ }
+
+ $classMap = require __DIR__ . '/autoload_classmap.php';
+ if ($classMap) {
+ $loader->addClassMap($classMap);
+ }
+
+ $loader->register(false);
+
+ return $loader;
+ }
+}
+
+function composerRequire9b10cc5cf6896d6e4a31983fc3498786($file)
+{
+ require $file;
+}
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
new file mode 100644
index 00000000..e882cda4
--- /dev/null
+++ b/vendor/composer/installed.json
@@ -0,0 +1,394 @@
+[
+ {
+ "name": "wikimedia/composer-merge-plugin",
+ "version": "v1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/composer-merge-plugin.git",
+ "reference": "ed426b785f9f786b33be4fd78584e43f4e962356"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/ed426b785f9f786b33be4fd78584e43f4e962356",
+ "reference": "ed426b785f9f786b33be4fd78584e43f4e962356",
+ "shasum": ""
+ },
+ "require": {
+ "composer-plugin-api": "1.0.0",
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "composer/composer": "1.0.*@dev",
+ "jakub-onderka/php-parallel-lint": "~0.8",
+ "phpspec/prophecy-phpunit": "~1.0",
+ "phpunit/phpunit": "~4.0",
+ "squizlabs/php_codesniffer": "~2.1.0"
+ },
+ "time": "2015-02-21 00:57:13",
+ "type": "composer-plugin",
+ "extra": {
+ "class": "Wikimedia\\Composer\\MergePlugin"
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "psr-4": {
+ "Wikimedia\\Composer\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Composer plugin to merge multiple composer.json files"
+ },
+ {
+ "name": "cssjanus/cssjanus",
+ "version": "v1.1.1",
+ "version_normalized": "1.1.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/cssjanus/php-cssjanus.git",
+ "reference": "62a9c32e6e140de09082b40a6e99d868ad14d4e0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/cssjanus/php-cssjanus/zipball/62a9c32e6e140de09082b40a6e99d868ad14d4e0",
+ "reference": "62a9c32e6e140de09082b40a6e99d868ad14d4e0",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "0.8.*",
+ "phpunit/phpunit": "3.7.*",
+ "squizlabs/php_codesniffer": "1.*"
+ },
+ "time": "2014-11-14 20:00:50",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "description": "Convert CSS stylesheets between left-to-right and right-to-left."
+ },
+ {
+ "name": "leafo/lessphp",
+ "version": "v0.5.0",
+ "version_normalized": "0.5.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/leafo/lessphp.git",
+ "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/leafo/lessphp/zipball/0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
+ "reference": "0f5a7f5545d2bcf4e9fad9a228c8ad89cc9aa283",
+ "shasum": ""
+ },
+ "time": "2014-11-24 18:39:20",
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.4.x-dev"
+ }
+ },
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "lessc.inc.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Leaf Corcoran",
+ "email": "leafot@gmail.com",
+ "homepage": "http://leafo.net"
+ }
+ ],
+ "description": "lessphp is a compiler for LESS written in PHP.",
+ "homepage": "http://leafo.net/lessphp/"
+ },
+ {
+ "name": "liuggio/statsd-php-client",
+ "version": "v1.0.12",
+ "version_normalized": "1.0.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/liuggio/statsd-php-client.git",
+ "reference": "a8c9ccd2a3af6cc49c7fc4f5f689d7b148ab19d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/liuggio/statsd-php-client/zipball/a8c9ccd2a3af6cc49c7fc4f5f689d7b148ab19d7",
+ "reference": "a8c9ccd2a3af6cc49c7fc4f5f689d7b148ab19d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.2"
+ },
+ "require-dev": {
+ "monolog/monolog": ">=1.2.0"
+ },
+ "suggest": {
+ "monolog/monolog": "Monolog, in order to do generate statistic from log >=1.2.0)"
+ },
+ "time": "2014-09-17 21:37:49",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Liuggio": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Giulio De Donato",
+ "email": "liuggio@gmail.com"
+ }
+ ],
+ "description": "Statsd (Object Oriented) client library for PHP",
+ "homepage": "https://github.com/liuggio/statsd-php-client/",
+ "keywords": [
+ "etsy",
+ "monitoring",
+ "php",
+ "statsd"
+ ]
+ },
+ {
+ "name": "oojs/oojs-ui",
+ "version": "v0.11.3",
+ "version_normalized": "0.11.3.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/oojs-ui.git",
+ "reference": "a03de5681e28e4fad1e27f8cccab32a2c5b484e5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/oojs-ui/zipball/a03de5681e28e4fad1e27f8cccab32a2c5b484e5",
+ "reference": "a03de5681e28e4fad1e27f8cccab32a2c5b484e5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "0.8.*",
+ "mediawiki/mediawiki-codesniffer": "0.1.0",
+ "squizlabs/php_codesniffer": "2.1.*"
+ },
+ "time": "2015-05-12 11:58:55",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "php/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Provides library of common widgets, layouts, and windows.",
+ "homepage": "https://www.mediawiki.org/wiki/OOjs_UI"
+ },
+ {
+ "name": "psr/log",
+ "version": "1.0.0",
+ "version_normalized": "1.0.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/log.git",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b",
+ "shasum": ""
+ },
+ "time": "2012-12-21 11:40:51",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "psr-0": {
+ "Psr\\Log\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "description": "Common interface for logging libraries",
+ "keywords": [
+ "log",
+ "psr",
+ "psr-3"
+ ]
+ },
+ {
+ "name": "wikimedia/cdb",
+ "version": "1.0.1",
+ "version_normalized": "1.0.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/cdb.git",
+ "reference": "3b7d5366c88eccf2517ebac57c59eb557c82f46c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/cdb/zipball/3b7d5366c88eccf2517ebac57c59eb557c82f46c",
+ "reference": "3b7d5366c88eccf2517ebac57c59eb557c82f46c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "*"
+ },
+ "time": "2014-12-08 19:26:44",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Tim Starling",
+ "email": "tstarling@wikimedia.org"
+ },
+ {
+ "name": "Chad Horohoe",
+ "email": "chad@wikimedia.org"
+ }
+ ],
+ "description": "Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.",
+ "homepage": "https://www.mediawiki.org/wiki/CDB"
+ },
+ {
+ "name": "wikimedia/utfnormal",
+ "version": "v1.0.2",
+ "version_normalized": "1.0.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/wikimedia/utfnormal.git",
+ "reference": "bb892a53a76116ad0982445a849043687cb6e778"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/wikimedia/utfnormal/zipball/bb892a53a76116ad0982445a849043687cb6e778",
+ "reference": "bb892a53a76116ad0982445a849043687cb6e778",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "ext-mbstring": "*",
+ "jakub-onderka/php-parallel-lint": "0.8.*",
+ "mediawiki/mediawiki-codesniffer": "0.1.0",
+ "phpunit/phpunit": "4.4.*",
+ "squizlabs/php_codesniffer": "2.1.*"
+ },
+ "time": "2015-03-12 01:54:47",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-2.0+"
+ ],
+ "authors": [
+ {
+ "name": "Brion Vibber",
+ "email": "bvibber@wikimedia.org"
+ }
+ ],
+ "homepage": "https://www.mediawiki.org/wiki/utfnormal"
+ },
+ {
+ "name": "zordius/lightncandy",
+ "version": "v0.18",
+ "version_normalized": "0.18.0.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/zordius/lightncandy.git",
+ "reference": "24be6909c37391f4648ce1fdf19036b11bd56d05"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/zordius/lightncandy/zipball/24be6909c37391f4648ce1fdf19036b11bd56d05",
+ "reference": "24be6909c37391f4648ce1fdf19036b11bd56d05",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.0.17"
+ },
+ "time": "2015-01-01 04:37:19",
+ "type": "library",
+ "installation-source": "dist",
+ "autoload": {
+ "classmap": [
+ "src/lightncandy.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Zordius Chen",
+ "email": "zordius@yahoo-inc.com"
+ }
+ ],
+ "description": "An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).",
+ "homepage": "https://github.com/zordius/lightncandy",
+ "keywords": [
+ "handlebars",
+ "logicless",
+ "mustache",
+ "php",
+ "template"
+ ]
+ }
+]
diff --git a/vendor/cssjanus/cssjanus/APACHE-LICENSE-2.0.txt b/vendor/cssjanus/cssjanus/APACHE-LICENSE-2.0.txt
new file mode 100644
index 00000000..d6456956
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/APACHE-LICENSE-2.0.txt
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/cssjanus/cssjanus/README.md b/vendor/cssjanus/cssjanus/README.md
new file mode 100644
index 00000000..597ef211
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/README.md
@@ -0,0 +1,45 @@
+[![Build Status](https://travis-ci.org/cssjanus/php-cssjanus.svg?branch=master)](https://travis-ci.org/cssjanus/php-cssjanus) [![Latest Stable Version](https://poser.pugx.org/cssjanus/cssjanus/v/stable.svg)](https://packagist.org/packages/cssjanus/cssjanus)
+
+# CSSJanus
+
+Convert CSS stylesheets between left-to-right and right-to-left.
+
+## Basic usage
+
+```php
+$rtlCss = CSSJanus::transform( $ltrCss );
+```
+
+## Advanced usage
+
+``transform( $css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false )``
+
+* ``$css`` (string) Stylesheet to transform
+* ``$swapLtrRtlInURL`` (boolean) Swap 'ltr' and 'rtl' in URLs
+* ``$swapLeftRightInURL`` (boolean) Swap 'left' and 'right' in URLs
+
+### Preventing flipping
+
+Use a ```/* @noflip */``` comment to protect a rule from being changed.
+
+```css
+.rule1 {
+ /* Will be converted to margin-right */
+ margin-left: 1em;
+}
+/* @noflip */
+.rule2 {
+ /* Will be preserved as margin-left */
+ margin-left: 1em;
+}
+```
+
+## Port
+
+This is a PHP port of the Node.js implementation of CSSJanus.
+
+Feature requests and bugs related to the actual CSS transformation or test
+cases of it, should be submitted upstream at
+<https://github.com/cssjanus/cssjanus>.
+
+Upstream releases will be ported here.
diff --git a/vendor/cssjanus/cssjanus/composer.json b/vendor/cssjanus/cssjanus/composer.json
new file mode 100644
index 00000000..7ac36936
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "cssjanus/cssjanus",
+ "description": "Convert CSS stylesheets between left-to-right and right-to-left.",
+ "repositories": [
+ {
+ "type": "vcs",
+ "url": "https://github.com/cssjanus/php-cssjanus"
+ }
+ ],
+ "license": "Apache-2.0",
+ "autoload": {
+ "psr-0": {
+ "": "src/"
+ }
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "0.8.*",
+ "phpunit/phpunit": "3.7.*",
+ "squizlabs/php_codesniffer": "1.*"
+ }
+}
diff --git a/vendor/cssjanus/cssjanus/src/CSSJanus.php b/vendor/cssjanus/cssjanus/src/CSSJanus.php
new file mode 100644
index 00000000..d81aa2bb
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/src/CSSJanus.php
@@ -0,0 +1,454 @@
+<?php
+/**
+ * PHP port of CSSJanus.
+ * https://github.com/cssjanus/php-cssjanus
+ *
+ * Copyright 2008 Google Inc.
+ * Copyright 2010 Roan Kattouw
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * @file
+ */
+
+/**
+ * This is a PHP port of CSSJanus, a utility that transforms CSS style sheets
+ * written for LTR to RTL.
+ *
+ * Original code: http://code.google.com/p/cssjanus/source/browse/trunk/cssjanus.py
+ *
+ * @author Lindsey Simon <elsigh@google.com>
+ * @author Roan Kattouw
+ */
+class CSSJanus {
+ // Patterns defined as null are built dynamically by buildPatterns()
+ private static $patterns = array(
+ 'tmpToken' => '`TMP`',
+ 'nonAscii' => '[\200-\377]',
+ 'unicode' => '(?:(?:\\[0-9a-f]{1,6})(?:\r\n|\s)?)',
+ 'num' => '(?:[0-9]*\.[0-9]+|[0-9]+)',
+ 'unit' => '(?:em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)',
+ 'body_selector' => 'body\s*{\s*',
+ 'direction' => 'direction\s*:\s*',
+ 'escape' => null,
+ 'nmstart' => null,
+ 'nmchar' => null,
+ 'ident' => null,
+ 'quantity' => null,
+ 'possibly_negative_quantity' => null,
+ 'color' => null,
+ 'url_special_chars' => '[!#$%&*-~]',
+ 'valid_after_uri_chars' => '[\'\"]?\s*',
+ 'url_chars' => null,
+ 'lookahead_not_open_brace' => null,
+ 'lookahead_not_closing_paren' => null,
+ 'lookahead_for_closing_paren' => null,
+ 'lookahead_not_letter' => '(?![a-zA-Z])',
+ 'lookbehind_not_letter' => '(?<![a-zA-Z])',
+ 'chars_within_selector' => '[^\}]*?',
+ 'noflip_annotation' => '\/\*\!?\s*@noflip\s*\*\/',
+ 'noflip_single' => null,
+ 'noflip_class' => null,
+ 'comment' => '/\/\*[^*]*\*+([^\/*][^*]*\*+)*\//',
+ 'direction_ltr' => null,
+ 'direction_rtl' => null,
+ 'left' => null,
+ 'right' => null,
+ 'left_in_url' => null,
+ 'right_in_url' => null,
+ 'ltr_in_url' => null,
+ 'rtl_in_url' => null,
+ 'cursor_east' => null,
+ 'cursor_west' => null,
+ 'four_notation_quantity' => null,
+ 'four_notation_color' => null,
+ 'border_radius' => null,
+ 'box_shadow' => null,
+ 'text_shadow1' => null,
+ 'text_shadow2' => null,
+ 'bg_horizontal_percentage' => null,
+ 'bg_horizontal_percentage_x' => null,
+ );
+
+ /**
+ * Build patterns we can't define above because they depend on other patterns.
+ */
+ private static function buildPatterns() {
+ if (!is_null(self::$patterns['escape'])) {
+ // Patterns have already been built
+ return;
+ }
+
+ // @codingStandardsIgnoreStart Generic.Files.LineLength.TooLong
+ $patterns =& self::$patterns;
+ $patterns['escape'] = "(?:{$patterns['unicode']}|\\[^\r\n\f0-9a-f])";
+ $patterns['nmstart'] = "(?:[_a-z]|{$patterns['nonAscii']}|{$patterns['escape']})";
+ $patterns['nmchar'] = "(?:[_a-z0-9-]|{$patterns['nonAscii']}|{$patterns['escape']})";
+ $patterns['ident'] = "-?{$patterns['nmstart']}{$patterns['nmchar']}*";
+ $patterns['quantity'] = "{$patterns['num']}(?:\s*{$patterns['unit']}|{$patterns['ident']})?";
+ $patterns['possibly_negative_quantity'] = "((?:-?{$patterns['quantity']})|(?:inherit|auto))";
+ $patterns['color'] = "(#?{$patterns['nmchar']}+|(?:rgba?|hsla?)\([ \d.,%-]+\))";
+ $patterns['url_chars'] = "(?:{$patterns['url_special_chars']}|{$patterns['nonAscii']}|{$patterns['escape']})*";
+ $patterns['lookahead_not_open_brace'] = "(?!({$patterns['nmchar']}|\r?\n|\s|#|\:|\.|\,|\+|>|\(|\)|\[|\]|=|\*=|~=|\^=|'[^']*'])*?{)";
+ $patterns['lookahead_not_closing_paren'] = "(?!{$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
+ $patterns['lookahead_for_closing_paren'] = "(?={$patterns['url_chars']}?{$patterns['valid_after_uri_chars']}\))";
+ $patterns['noflip_single'] = "/({$patterns['noflip_annotation']}{$patterns['lookahead_not_open_brace']}[^;}]+;?)/i";
+ $patterns['noflip_class'] = "/({$patterns['noflip_annotation']}{$patterns['chars_within_selector']}})/i";
+ $patterns['direction_ltr'] = "/({$patterns['direction']})ltr/i";
+ $patterns['direction_rtl'] = "/({$patterns['direction']})rtl/i";
+ $patterns['left'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
+ $patterns['right'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_not_letter']}{$patterns['lookahead_not_closing_paren']}{$patterns['lookahead_not_open_brace']}/i";
+ $patterns['left_in_url'] = "/{$patterns['lookbehind_not_letter']}(left){$patterns['lookahead_for_closing_paren']}/i";
+ $patterns['right_in_url'] = "/{$patterns['lookbehind_not_letter']}(right){$patterns['lookahead_for_closing_paren']}/i";
+ $patterns['ltr_in_url'] = "/{$patterns['lookbehind_not_letter']}(ltr){$patterns['lookahead_for_closing_paren']}/i";
+ $patterns['rtl_in_url'] = "/{$patterns['lookbehind_not_letter']}(rtl){$patterns['lookahead_for_closing_paren']}/i";
+ $patterns['cursor_east'] = "/{$patterns['lookbehind_not_letter']}([ns]?)e-resize/";
+ $patterns['cursor_west'] = "/{$patterns['lookbehind_not_letter']}([ns]?)w-resize/";
+ $patterns['four_notation_quantity_props'] = "((?:margin|padding|border-width)\s*:\s*)";
+ $patterns['four_notation_quantity'] = "/{$patterns['four_notation_quantity_props']}{$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s+){$patterns['possibly_negative_quantity']}(\s*(?:!important\s*)?[;}])/i";
+ $patterns['four_notation_color'] = "/((?:-color|border-style)\s*:\s*){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s+){$patterns['color']}(\s*(?:!important\s*)?[;}])/i";
+ $patterns['border_radius'] = "/(border-radius\s*:\s*)([^;}]*)/";
+ $patterns['box_shadow'] = "/(box-shadow\s*:\s*(?:inset\s*)?){$patterns['possibly_negative_quantity']}/i";
+ $patterns['text_shadow1'] = "/(text-shadow\s*:\s*){$patterns['color']}(\s*){$patterns['possibly_negative_quantity']}/i";
+ $patterns['text_shadow2'] = "/(text-shadow\s*:\s*){$patterns['possibly_negative_quantity']}/i";
+ $patterns['bg_horizontal_percentage'] = "/(background(?:-position)?\s*:\s*(?:[^:;}\s]+\s+)*?)({$patterns['quantity']})/i";
+ $patterns['bg_horizontal_percentage_x'] = "/(background-position-x\s*:\s*)(-?{$patterns['num']}%)/i";
+ // @codingStandardsIgnoreEnd
+
+ }
+
+ /**
+ * Transform an LTR stylesheet to RTL
+ * @param string $css stylesheet to transform
+ * @param $swapLtrRtlInURL Boolean: If true, swap 'ltr' and 'rtl' in URLs
+ * @param $swapLeftRightInURL Boolean: If true, swap 'left' and 'right' in URLs
+ * @return string Transformed stylesheet
+ */
+ public static function transform($css, $swapLtrRtlInURL = false, $swapLeftRightInURL = false) {
+ // We wrap tokens in ` , not ~ like the original implementation does.
+ // This was done because ` is not a legal character in CSS and can only
+ // occur in URLs, where we escape it to %60 before inserting our tokens.
+ $css = str_replace('`', '%60', $css);
+
+ self::buildPatterns();
+
+ // Tokenize single line rules with /* @noflip */
+ $noFlipSingle = new CSSJanusTokenizer(self::$patterns['noflip_single'], '`NOFLIP_SINGLE`');
+ $css = $noFlipSingle->tokenize($css);
+
+ // Tokenize class rules with /* @noflip */
+ $noFlipClass = new CSSJanusTokenizer(self::$patterns['noflip_class'], '`NOFLIP_CLASS`');
+ $css = $noFlipClass->tokenize($css);
+
+ // Tokenize comments
+ $comments = new CSSJanusTokenizer(self::$patterns['comment'], '`C`');
+ $css = $comments->tokenize($css);
+
+ // LTR->RTL fixes start here
+ $css = self::fixDirection($css);
+ if ($swapLtrRtlInURL) {
+ $css = self::fixLtrRtlInURL($css);
+ }
+
+ if ($swapLeftRightInURL) {
+ $css = self::fixLeftRightInURL($css);
+ }
+ $css = self::fixLeftAndRight($css);
+ $css = self::fixCursorProperties($css);
+ $css = self::fixFourPartNotation($css);
+ $css = self::fixBorderRadius($css);
+ $css = self::fixBackgroundPosition($css);
+ $css = self::fixShadows($css);
+
+ // Detokenize stuff we tokenized before
+ $css = $comments->detokenize($css);
+ $css = $noFlipClass->detokenize($css);
+ $css = $noFlipSingle->detokenize($css);
+
+ return $css;
+ }
+
+ /**
+ * Replace direction: ltr; with direction: rtl; and vice versa.
+ *
+ * The original implementation only does this inside body selectors
+ * and misses "body\n{\ndirection:ltr;\n}". This function does not have
+ * these problems.
+ *
+ * See https://code.google.com/p/cssjanus/issues/detail?id=15
+ *
+ * @param $css string
+ * @return string
+ */
+ private static function fixDirection($css) {
+ $css = preg_replace(
+ self::$patterns['direction_ltr'],
+ '$1' . self::$patterns['tmpToken'],
+ $css
+ );
+ $css = preg_replace(self::$patterns['direction_rtl'], '$1ltr', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'rtl', $css);
+
+ return $css;
+ }
+
+ /**
+ * Replace 'ltr' with 'rtl' and vice versa in background URLs
+ * @param $css string
+ * @return string
+ */
+ private static function fixLtrRtlInURL($css) {
+ $css = preg_replace(self::$patterns['ltr_in_url'], self::$patterns['tmpToken'], $css);
+ $css = preg_replace(self::$patterns['rtl_in_url'], 'ltr', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'rtl', $css);
+
+ return $css;
+ }
+
+ /**
+ * Replace 'left' with 'right' and vice versa in background URLs
+ * @param $css string
+ * @return string
+ */
+ private static function fixLeftRightInURL($css) {
+ $css = preg_replace(self::$patterns['left_in_url'], self::$patterns['tmpToken'], $css);
+ $css = preg_replace(self::$patterns['right_in_url'], 'left', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'right', $css);
+
+ return $css;
+ }
+
+ /**
+ * Flip rules like left: , padding-right: , etc.
+ * @param $css string
+ * @return string
+ */
+ private static function fixLeftAndRight($css) {
+ $css = preg_replace(self::$patterns['left'], self::$patterns['tmpToken'], $css);
+ $css = preg_replace(self::$patterns['right'], 'left', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'right', $css);
+
+ return $css;
+ }
+
+ /**
+ * Flip East and West in rules like cursor: nw-resize;
+ * @param $css string
+ * @return string
+ */
+ private static function fixCursorProperties($css) {
+ $css = preg_replace(
+ self::$patterns['cursor_east'],
+ '$1' . self::$patterns['tmpToken'],
+ $css
+ );
+ $css = preg_replace(self::$patterns['cursor_west'], '$1e-resize', $css);
+ $css = str_replace(self::$patterns['tmpToken'], 'w-resize', $css);
+
+ return $css;
+ }
+
+ /**
+ * Swap the second and fourth parts in four-part notation rules like
+ * padding: 1px 2px 3px 4px;
+ *
+ * Unlike the original implementation, this function doesn't suffer from
+ * the bug where whitespace is not preserved when flipping four-part rules
+ * and four-part color rules with multiple whitespace characters between
+ * colors are not recognized.
+ * See https://code.google.com/p/cssjanus/issues/detail?id=16
+ * @param $css string
+ * @return string
+ */
+ private static function fixFourPartNotation($css) {
+ $css = preg_replace(self::$patterns['four_notation_quantity'], '$1$2$3$8$5$6$7$4$9', $css);
+ $css = preg_replace(self::$patterns['four_notation_color'], '$1$2$3$8$5$6$7$4$9', $css);
+ return $css;
+ }
+
+ /**
+ * Swaps appropriate corners in border-radius values.
+ *
+ * @param $css string
+ * @return string
+ */
+ private static function fixBorderRadius($css) {
+ $css = preg_replace_callback(self::$patterns['border_radius'], function ($matches) {
+ $pre = $matches[1];
+ $values = $matches[2];
+ $numValues = count(preg_split('/\s+/', trim($values)));
+ switch ($numValues) {
+ case 4:
+ $values = preg_replace('/^(\S+)(\s*)(\S+)(\s*)(\S+)(\s*)(\S+)/', '$3$2$1$4$7$6$5', $values);
+ break;
+ case 3:
+ case 2:
+ $values = preg_replace('/^(\S+)(\s*)(\S+)/', '$3$2$1', $values);
+ break;
+ }
+ return $pre . $values;
+ }, $css);
+
+ return $css;
+ }
+
+ /**
+ * Negates horizontal offset in box-shadow and text-shadow rules.
+ *
+ * @param $css string
+ * @return string
+ */
+ private static function fixShadows($css) {
+ // Flips the sign of a CSS value, possibly with a unit.
+ // (We can't just negate the value with unary minus due to the units.)
+ $flipSign = function ($cssValue) {
+ // Don't mangle zeroes
+ if (floatval($cssValue) === 0.0) {
+ return $cssValue;
+ } elseif ($cssValue[0] === '-') {
+ return substr($cssValue, 1);
+ } else {
+ return "-" . $cssValue;
+ }
+ };
+
+ $css = preg_replace_callback(self::$patterns['box_shadow'], function ($matches) use ($flipSign) {
+ return $matches[1] . $flipSign($matches[2]);
+ }, $css);
+
+ $css = preg_replace_callback(self::$patterns['text_shadow1'], function ($matches) use ($flipSign) {
+ return $matches[1] . $matches[2] . $matches[3] . $flipSign($matches[4]);
+ }, $css);
+
+ $css = preg_replace_callback(self::$patterns['text_shadow2'], function ($matches) use ($flipSign) {
+ return $matches[1] . $flipSign($matches[2]);
+ }, $css);
+
+ return $css;
+ }
+
+ /**
+ * Flip horizontal background percentages.
+ * @param $css string
+ * @return string
+ */
+ private static function fixBackgroundPosition($css) {
+ $replaced = preg_replace_callback(
+ self::$patterns['bg_horizontal_percentage'],
+ array('self', 'calculateNewBackgroundPosition'),
+ $css
+ );
+ if ($replaced !== null) {
+ // preg_replace_callback() sometimes returns null
+ $css = $replaced;
+ }
+ $replaced = preg_replace_callback(
+ self::$patterns['bg_horizontal_percentage_x'],
+ array('self', 'calculateNewBackgroundPosition'),
+ $css
+ );
+ if ($replaced !== null) {
+ $css = $replaced;
+ }
+
+ return $css;
+ }
+
+ /**
+ * Callback for fixBackgroundPosition()
+ * @param $matches array
+ * @return string
+ */
+ private static function calculateNewBackgroundPosition($matches) {
+ $value = $matches[2];
+ if (substr($value, -1) === '%') {
+ $idx = strpos($value, '.');
+ if ($idx !== false) {
+ $len = strlen($value) - $idx - 2;
+ $value = number_format(100 - $value, $len) . '%';
+ } else {
+ $value = (100 - $value) . '%';
+ }
+ }
+ return $matches[1] . $value;
+ }
+}
+
+/**
+ * Utility class used by CSSJanus that tokenizes and untokenizes things we want
+ * to protect from being janused.
+ * @author Roan Kattouw
+ */
+class CSSJanusTokenizer {
+ private $regex;
+ private $token;
+ private $originals;
+
+ /**
+ * Constructor
+ * @param string $regex Regular expression whose matches to replace by a token.
+ * @param string $token Token
+ */
+ public function __construct($regex, $token) {
+ $this->regex = $regex;
+ $this->token = $token;
+ $this->originals = array();
+ }
+
+ /**
+ * Replace all occurrences of $regex in $str with a token and remember
+ * the original strings.
+ * @param string $str to tokenize
+ * @return string Tokenized string
+ */
+ public function tokenize($str) {
+ return preg_replace_callback($this->regex, array($this, 'tokenizeCallback'), $str);
+ }
+
+ /**
+ * @param $matches array
+ * @return string
+ */
+ private function tokenizeCallback($matches) {
+ $this->originals[] = $matches[0];
+ return $this->token;
+ }
+
+ /**
+ * Replace tokens with their originals. If multiple strings were tokenized, it's important they be
+ * detokenized in exactly the SAME ORDER.
+ * @param string $str previously run through tokenize()
+ * @return string Original string
+ */
+ public function detokenize($str) {
+ // PHP has no function to replace only the first occurrence or to
+ // replace occurrences of the same string with different values,
+ // so we use preg_replace_callback() even though we don't really need a regex
+ return preg_replace_callback(
+ '/' . preg_quote($this->token, '/') . '/',
+ array($this, 'detokenizeCallback'),
+ $str
+ );
+ }
+
+ /**
+ * @param $matches
+ * @return mixed
+ */
+ private function detokenizeCallback($matches) {
+ $retval = current($this->originals);
+ next($this->originals);
+
+ return $retval;
+ }
+}
diff --git a/vendor/cssjanus/cssjanus/test/codesniffer/ruleset.xml b/vendor/cssjanus/cssjanus/test/codesniffer/ruleset.xml
new file mode 100644
index 00000000..dab0fe2a
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/test/codesniffer/ruleset.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<ruleset name="cssjanus">
+ <rule ref="PSR2">
+ <exclude name="PSR1.Classes.ClassDeclaration.MissingNamespace" />
+ <exclude name="PSR1.Classes.ClassDeclaration.MultipleClasses" />
+ <exclude name="PSR2.Classes.ClassDeclaration.OpenBraceNewLine" />
+ <exclude name="Squiz.Functions.MultiLineFunctionDeclaration.BraceOnSameLine" />
+ </rule>
+</ruleset>
diff --git a/vendor/cssjanus/cssjanus/test/phpunit.xml b/vendor/cssjanus/cssjanus/test/phpunit.xml
new file mode 100644
index 00000000..6d37a1c4
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/test/phpunit.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit colors="true" strict="true" bootstrap="../vendor/autoload.php">
+ <testsuites>
+ <testsuite name="CSSJanus Test Suite">
+ <directory>./suites</directory>
+ </testsuite>
+ </testsuites>
+</phpunit>
diff --git a/vendor/cssjanus/cssjanus/test/run b/vendor/cssjanus/cssjanus/test/run
new file mode 100644
index 00000000..247500c7
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/test/run
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+set -e
+cd $(cd $(dirname $0)/..; pwd)
+set -x
+./vendor/bin/parallel-lint --exclude vendor .
+./vendor/bin/phpunit --configuration test/phpunit.xml
+./vendor/bin/phpcs . --standard=./test/codesniffer --ignore=vendor/* --report=full -s --tab-width=4
diff --git a/vendor/cssjanus/cssjanus/test/suites/CSSJanusTest.php b/vendor/cssjanus/cssjanus/test/suites/CSSJanusTest.php
new file mode 100644
index 00000000..8a436760
--- /dev/null
+++ b/vendor/cssjanus/cssjanus/test/suites/CSSJanusTest.php
@@ -0,0 +1,71 @@
+<?php
+
+class CSSJanusTest extends PHPUnit_Framework_TestCase {
+
+ public static function provideData() {
+ $data = self::getSpec();
+ $cases = array();
+ $defaultSettings = array(
+ 'swapLtrRtlInUrl' => false,
+ 'swapLeftRightInUrl' => false,
+ );
+ foreach ($data as $name => $test) {
+ $settings = isset($test['settings']) ? $test['settings'] : array();
+ $settings += $defaultSettings;
+ foreach ($test['cases'] as $i => $case) {
+ $input = $case[0];
+ $noop = !isset($case[1]);
+ $output = $noop ? $input : $case[1];
+
+ $cases[] = array(
+ $input,
+ $settings,
+ $output,
+ $name,
+ );
+
+ if (!$noop) {
+ // Round trip
+ $cases[] = array(
+ $output,
+ $settings,
+ $input,
+ $name,
+ );
+ }
+ }
+ }
+ return $cases;
+ }
+
+ /**
+ * @dataProvider provideData
+ */
+ public function testTransform($input, $settings, $output, $name) {
+ $this->assertEquals(
+ $output,
+ CSSJanus::transform($input, $settings['swapLtrRtlInUrl'], $settings['swapLeftRightInUrl']),
+ $name
+ );
+ }
+
+ protected static function getSpec() {
+ static $json;
+ if ($json == null) {
+ $version = '1.1.1';
+ $dir = dirname(__DIR__);
+ $file = "$dir/data-v$version.json";
+ if (!is_readable($file)) {
+ array_map('unlink', glob("$dir/data-v*.json"));
+ $json = file_get_contents("https://github.com/cssjanus/cssjanus/raw/v$version/test/data.json");
+ if ($json === false) {
+ throw new Exception('Failed to fetch data');
+ }
+ file_put_contents($file, $json);
+ } else {
+ $json = file_get_contents($file);
+ }
+ }
+ return json_decode($json, /* $assoc = */ true);
+ }
+}
diff --git a/vendor/leafo/lessphp/LICENSE b/vendor/leafo/lessphp/LICENSE
new file mode 100644
index 00000000..49c9ea41
--- /dev/null
+++ b/vendor/leafo/lessphp/LICENSE
@@ -0,0 +1,660 @@
+For ease of distribution, lessphp 0.4.0 is under a dual license.
+You are free to pick which one suits your needs.
+
+
+
+
+MIT LICENSE
+
+
+
+
+Copyright (c) 2013 Leaf Corcoran, http://leafo.net/lessphp
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+
+
+GPL VERSION 3
+
+
+
+
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
diff --git a/vendor/leafo/lessphp/Makefile b/vendor/leafo/lessphp/Makefile
new file mode 100644
index 00000000..a5d262cd
--- /dev/null
+++ b/vendor/leafo/lessphp/Makefile
@@ -0,0 +1,7 @@
+
+test:
+ phpunit --colors tests
+
+release:
+ ./package.sh
+
diff --git a/vendor/leafo/lessphp/README.md b/vendor/leafo/lessphp/README.md
new file mode 100644
index 00000000..1379ddce
--- /dev/null
+++ b/vendor/leafo/lessphp/README.md
@@ -0,0 +1,96 @@
+[![Build Status](https://travis-ci.org/leafo/lessphp.svg?branch=master)](https://travis-ci.org/leafo/lessphp)
+
+# lessphp v0.5.0
+### <http://leafo.net/lessphp>
+
+`lessphp` is a compiler for LESS written in PHP. The documentation is great,
+so check it out: <http://leafo.net/lessphp/docs/>.
+
+Here's a quick tutorial:
+
+### How to use in your PHP project
+
+The only file required is `lessc.inc.php`, so copy that to your include directory.
+
+The typical flow of **lessphp** is to create a new instance of `lessc`,
+configure it how you like, then tell it to compile something using one built in
+compile methods.
+
+The `compile` method compiles a string of LESS code to CSS.
+
+```php
+<?php
+require "lessc.inc.php";
+
+$less = new lessc;
+echo $less->compile(".block { padding: 3 + 4px }");
+```
+
+The `compileFile` method reads and compiles a file. It will either return the
+result or write it to the path specified by an optional second argument.
+
+```php
+<?php
+echo $less->compileFile("input.less");
+```
+
+The `compileChecked` method is like `compileFile`, but it only compiles if the output
+file doesn't exist or it's older than the input file:
+
+```php
+<?php
+$less->checkedCompile("input.less", "output.css");
+```
+
+If there any problem compiling your code, an exception is thrown with a helpful message:
+
+```php
+<?php
+try {
+ $less->compile("invalid LESS } {");
+} catch (exception $e) {
+ echo "fatal error: " . $e->getMessage();
+}
+```
+
+The `lessc` object can be configured through an assortment of instance methods.
+Some possible configuration options include [changing the output format][1],
+[setting variables from PHP][2], and [controlling the preservation of
+comments][3], writing [custom functions][4] and much more. It's all described
+in [the documentation][0].
+
+
+ [0]: http://leafo.net/lessphp/docs/
+ [1]: http://leafo.net/lessphp/docs/#output_formatting
+ [2]: http://leafo.net/lessphp/docs/#setting_variables_from_php
+ [3]: http://leafo.net/lessphp/docs/#preserving_comments
+ [4]: http://leafo.net/lessphp/docs/#custom_functions
+
+
+### How to use from the command line
+
+An additional script has been included to use the compiler from the command
+line. In the simplest invocation, you specify an input file and the compiled
+css is written to standard out:
+
+ $ plessc input.less > output.css
+
+Using the -r flag, you can specify LESS code directly as an argument or, if
+the argument is left off, from standard in:
+
+ $ plessc -r "my less code here"
+
+Finally, by using the -w flag you can watch a specified input file and have it
+compile as needed to the output file:
+
+ $ plessc -w input-file output-file
+
+Errors from watch mode are written to standard out.
+
+The -f flag sets the [output formatter][1]. For example, to compress the
+output run this:
+
+ $ plessc -f=compressed myfile.less
+
+For more help, run `plessc --help`
+
diff --git a/vendor/leafo/lessphp/composer.json b/vendor/leafo/lessphp/composer.json
new file mode 100644
index 00000000..0f06ba09
--- /dev/null
+++ b/vendor/leafo/lessphp/composer.json
@@ -0,0 +1,25 @@
+{
+ "name": "leafo/lessphp",
+ "type": "library",
+ "description": "lessphp is a compiler for LESS written in PHP.",
+ "homepage": "http://leafo.net/lessphp/",
+ "license": [
+ "MIT",
+ "GPL-3.0"
+ ],
+ "authors": [
+ {
+ "name": "Leaf Corcoran",
+ "email": "leafot@gmail.com",
+ "homepage": "http://leafo.net"
+ }
+ ],
+ "autoload": {
+ "classmap": ["lessc.inc.php"]
+ },
+ "extra": {
+ "branch-alias": {
+ "dev-master": "0.4.x-dev"
+ }
+ }
+}
diff --git a/vendor/leafo/lessphp/docs/docs.md b/vendor/leafo/lessphp/docs/docs.md
new file mode 100644
index 00000000..112dc2ee
--- /dev/null
+++ b/vendor/leafo/lessphp/docs/docs.md
@@ -0,0 +1,1400 @@
+ title: v0.5.0 documentation
+ link_to_home: true
+--
+
+<h2 skip="true">Documentation v0.5.0</h2>
+
+<div style="margin-bottom: 1em;">$index</div>
+
+**lessphp** is a compiler that generates CSS from a superset language which
+adds a collection of convenient features often seen in other languages. All CSS
+is compatible with LESS, so you can start using new features with your existing CSS.
+
+It is designed to be compatible with [less.js](http://lesscss.org), and suitable
+as a drop in replacement for PHP projects.
+
+## Getting Started
+
+The homepage for **lessphp** can be found at [http://leafo.net/lessphp/][1].
+
+You can follow development at the project's [GitHub][2].
+
+Including **lessphp** in your project is as simple as dropping the single
+include file into your code base and running the appropriate compile method as
+described in the [PHP Interface](#php_interface).
+
+ [1]: http://leafo.net/lessphp "lessphp homepage"
+ [2]: https://github.com/leafo/lessphp "lessphp GitHub page"
+
+## Installation
+
+**lessphp** is distributed entirely in a single stand-alone file. Download the
+latest version from either [the homepage][1] or [GitHub][2].
+
+Development versions can also be downloading from GitHub.
+
+Place `lessphp.inc.php` in a location available to your PHP scripts, and
+include it. That's it! you're ready to begin.
+
+## The Language
+
+**lessphp** is very easy to learn because it generally functions how you would
+expect it to. If you feel something is challenging or missing, feel free to
+open an issue on the [bug tracker](https://github.com/leafo/lessphp/issues).
+
+It is also easy to learn because any standards-compliant CSS code is valid LESS
+code. You are free to gradually enhance your existing CSS code base with LESS
+features without having to worry about rewriting anything.
+
+The following is a description of the new languages features provided by LESS.
+
+### Line Comments
+
+Simple but very useful; line comments are started with `//`:
+
+ ```less
+ // this is a comment
+ body {
+ color: red; // as is this
+ /* block comments still work also */
+ }
+ ```
+
+### Variables
+
+Variables are identified with a name that starts with `@`. To declare a
+variable, you create an appropriately named CSS property and assign it a value:
+
+ ```less
+ @family: "verdana";
+ @color: red;
+ body {
+ @mycolor: red;
+ font-family: @family;
+ color: @color;
+ border-bottom: 1px solid @color;
+ }
+ ```
+
+Variable declarations will not appear in the output. Variables can be declared
+in the outer most scope of the file, or anywhere else a CSS property may
+appear. They can hold any CSS property value.
+
+Variables are only visible for use from their current scope, or any enclosed
+scopes.
+
+If you have a string or keyword in a variable, you can reference another
+variable by that name by repeating the `@`:
+
+ ```less
+ @value: 20px;
+ @value_name: "value";
+
+ width: @@value_name;
+ ```
+
+### Expressions
+
+Expressions let you combine values and variables in meaningful ways. For
+example you can add to a color to make it a different shade. Or divide up the
+width of your layout logically. You can even concatenate strings.
+
+Use the mathematical operators to evaluate an expression:
+
+ ```less
+ @width: 960px;
+ .nav {
+ width: @width / 3;
+ color: #001 + #abc;
+ }
+ .body {
+ width: 2 * @width / 3;
+ font-family: "hel" + "vetica";
+ }
+ ```
+
+Parentheses can be used to control the order of evaluation. They can also be
+used to force an evaluation for cases where CSS's syntax makes the expression
+ambiguous.
+
+The following property will produce two numbers, instead of doing the
+subtraction:
+
+ ```less
+ margin: 10px -5px;
+ ```
+
+To force the subtraction:
+
+ ```less
+ margin: (10px -5px);
+ ```
+
+It is also safe to surround mathematical operators by spaces to ensure that
+they are evaluated:
+
+ ```less
+ margin: 10px - 5px;
+ ```
+
+Division has a special quirk. There are certain CSS properties that use the `/`
+operator as part of their value's syntax. Namely, the [font][4] shorthand and
+[border-radius][3].
+
+ [3]: https://developer.mozilla.org/en/CSS/border-radius
+ [4]: https://developer.mozilla.org/en/CSS/font
+
+
+Thus, **lessphp** will ignore any division in these properties unless it is
+wrapped in parentheses. For example, no division will take place here:
+
+ ```less
+ .font {
+ font: 20px/80px "Times New Roman";
+ }
+ ```
+
+In order to force division we must wrap the expression in parentheses:
+
+ ```less
+ .font {
+ font: (20px/80px) "Times New Roman";
+ }
+ ```
+
+If you want to write a literal `/` expression without dividing in another
+property (or a variable), you can use [string unquoting](#string_unquoting):
+
+ ```less
+ .var {
+ @size: ~"20px/80px";
+ font: @size sans-serif;
+ }
+ ```
+
+### Nested Blocks
+
+By nesting blocks we can build up a chain of CSS selectors through scope
+instead of repeating them. In addition to reducing repetition, this also helps
+logically organize the structure of our CSS.
+
+ ```less
+ ol.list {
+ li.special {
+ border: 1px solid red;
+ }
+
+ li.plain {
+ font-weight: bold;
+ }
+ }
+ ```
+
+
+This will produce two blocks, a `ol.list li.special` and `ol.list li.plain`.
+
+Blocks can be nested as deep as required in order to build a hierarchy of
+relationships.
+
+The `&` operator can be used in a selector to represent its parent's selector.
+If the `&` operator is used, then the default action of appending the parent to
+the front of the child selector separated by space is not performed.
+
+ ```less
+ b {
+ a & {
+ color: red;
+ }
+
+ // the following have the same effect
+
+ & i {
+ color: blue;
+ }
+
+ i {
+ color: blue;
+ }
+ }
+ ```
+
+
+Because the `&` operator respects the whitespace around it, we can use it to
+control how the child blocks are joined. Consider the differences between the
+following:
+
+ ```less
+ div {
+ .child-class { color: purple; }
+
+ &.isa-class { color: green; }
+
+ #child-id { height: 200px; }
+
+ &#div-id { height: 400px; }
+
+ &:hover { color: red; }
+
+ :link { color: blue; }
+ }
+ ```
+
+The `&` operator also works with [mixins](#mixins), which produces interesting results:
+
+ ```less
+ .within_box_style() {
+ .box & {
+ color: blue;
+ }
+ }
+
+ #menu {
+ .within_box_style;
+ }
+ ```
+
+### Mixins
+
+Any block can be mixed in just by naming it:
+
+ ```less
+ .mymixin {
+ color: blue;
+ border: 1px solid red;
+
+ .special {
+ font-weight: bold;
+ }
+ }
+
+
+ h1 {
+ font-size: 200px;
+ .mymixin;
+ }
+ ```
+
+All properties and child blocks are mixed in.
+
+Mixins can be made parametric, meaning they can take arguments, in order to
+enhance their utility. A parametric mixin all by itself is not outputted when
+compiled. Its properties will only appear when mixed into another block.
+
+The canonical example is to create a rounded corners mixin that works across
+browsers:
+
+ ```less
+ .rounded-corners(@radius: 5px) {
+ border-radius: @radius;
+ -webkit-border-radius: @radius;
+ -moz-border-radius: @radius;
+ }
+
+ .header {
+ .rounded-corners();
+ }
+
+ .info {
+ background: red;
+ .rounded-corners(14px);
+ }
+ ```
+
+If you have a mixin that doesn't have any arguments, but you don't want it to
+show up in the output, give it a blank argument list:
+
+ ```less
+ .secret() {
+ font-size: 6000px;
+ }
+
+ .div {
+ .secret;
+ }
+ ```
+
+If the mixin doesn't need any arguments, you can leave off the parentheses when
+mixing it in, as seen above.
+
+You can also mixin a block that is nested inside other blocks. You can think of
+the outer block as a way of making a scope for your mixins. You just list the
+names of the mixins separated by spaces, which describes the path to the mixin
+you want to include. Optionally you can separate them by `>`.
+
+ ```less
+ .my_scope {
+ .some_color {
+ color: red;
+ .inner_block {
+ text-decoration: underline;
+ }
+ }
+ .bold {
+ font-weight: bold;
+ color: blue;
+ }
+ }
+
+ .a_block {
+ .my_scope .some_color;
+ .my_scope .some_color .inner_block;
+ }
+
+ .another_block {
+ // the alternative syntax
+ .my_scope > .bold;
+ }
+ ```
+
+
+#### Mixin Arguments
+
+When declaring a mixin you can specify default values for each argument. Any
+argument left out will be given the default value specified. Here's the
+syntax:
+
+```less
+.mix(@color: red, @height: 20px, @pad: 12px) {
+ border: 1px solid @color;
+ height: @height - @pad;
+ padding: @pad;
+}
+
+.default1 {
+ .mix();
+}
+
+.default2 {
+ .mix(blue);
+}
+
+.default3 {
+ .mix(blue, 40px, 5px);
+}
+```
+
+Additionally, you can also call a mixin using the argument names, this is
+useful if you want to replace a specific argument while having all the others
+take the default regardless of what position the argument appears in. The
+syntax looks something like this:
+
+
+```lessbasic
+div {
+ .my_mixin(@paddding: 4px); // @color and @height get default values
+ .my_mixin(@paddding: 4px, @height: 50px); // you can specify them in any order
+}
+```
+
+You can also combine the ordered arguments with the named ones:
+
+```lessbasic
+div {
+ // @color is blue, @padding is 4px, @height is default
+ .my_mixin(blue, @padding: 4px);
+}
+```
+
+Mixin arguments can be delimited with either a `,` or `;`, but only one can be
+active at once. This means that each argument is separated by either `,` or
+`;`. By default `,` is the delimiter, in all the above examples we used a `,`.
+
+A problem arises though, sometimes CSS value lists are made up with commas. In
+order to be able to pass a comma separated list literal we need to use `;` as
+the delimiter. (You don't need to worry about this if your list is stored in a
+variable)
+
+If a `;` appears anywhere in the argument list, then it will be used as the
+argument delimiter, and all commas we be used as part of the argument values.
+
+Here's a basic example:
+
+```less
+.fancy_mixin(@box_shadow, @color: blue) {
+ border: 1px solid @color;
+ box-shadow: @box_shadow;
+}
+
+
+div {
+ // two arguments passed separated by ;
+ .fancy_mixin(2px 2px, -2px -2px; red);
+}
+
+pre {
+ // one argument passed, ends in ;
+ .fancy_mixin(inset 4px 4px, -2px 2px;);
+}
+
+```
+
+If we only want to pass a single comma separated value we still need to use
+`;`, to do this we stick it on the end as demonstrated above.
+
+
+#### `@arguments` Variable
+
+Within an mixin there is a special variable named `@arguments` that contains
+all the arguments passed to the mixin along with any remaining arguments that
+have default values. The value of the variable has all the values separated by
+spaces.
+
+This useful for quickly assigning all the arguments:
+
+ ```less
+ .box-shadow(@x, @y, @blur, @color) {
+ box-shadow: @arguments;
+ -webkit-box-shadow: @arguments;
+ -moz-box-shadow: @arguments;
+ }
+ .menu {
+ .box-shadow(1px, 1px, 5px, #aaa);
+ }
+ ```
+
+In addition to the arguments passed to the mixin, `@arguments` will also include
+remaining default values assigned by the mixin:
+
+
+ ```less
+ .border-mixin(@width, @style: solid, @color: black) {
+ border: @arguments;
+ }
+
+ pre {
+ .border-mixin(4px, dotted);
+ }
+
+ ```
+
+
+#### Pattern Matching
+
+When you *mix in* a mixin, all the available mixins of that name in the current
+scope are checked to see if they match based on what was passed to the mixin
+and how it was declared.
+
+The simplest case is matching by number of arguments. Only the mixins that
+match the number of arguments passed in are used.
+
+ ```less
+ .simple() { // matches no arguments
+ height: 10px;
+ }
+
+ .simple(@a, @b) { // matches two arguments
+ color: red;
+ }
+
+ .simple(@a) { // matches one argument
+ color: blue;
+ }
+
+ div {
+ .simple(10);
+ }
+
+ span {
+ .simple(10, 20);
+ }
+ ```
+
+Whether an argument has default values is also taken into account when matching
+based on number of arguments:
+
+ ```less
+ // matches one or two arguments
+ .hello(@a, @b: blue) {
+ height: @a;
+ color: @b;
+ }
+
+ .hello(@a, @b) { // matches only two
+ width: @a;
+ border-color: @b;
+ }
+
+ .hello(@a) { // matches only one
+ padding: 1em;
+ }
+
+ div {
+ .hello(10px);
+ }
+
+ pre {
+ .hello(10px, yellow);
+ }
+ ```
+
+Additionally, a *vararg* value can be used to further control how things are
+matched. A mixin's argument list can optionally end in the special argument
+named `...`. The `...` may match any number of arguments, including 0.
+
+ ```less
+ // this will match any number of arguments
+ .first(...) {
+ color: blue;
+ }
+
+ // matches at least 1 argument
+ .second(@arg, ...) {
+ height: 200px + @arg;
+ }
+
+ div { .first("some", "args"); }
+ pre { .second(10px); }
+ ```
+
+If you want to capture the values that get captured by the *vararg* you can
+give it a variable name by putting it directly before the `...`. This variable
+must be the last argument defined. It's value is just like the special
+[`@arguments` variable](#arguments_variable), a space separated list.
+
+
+ ```less
+ .hello(@first, @rest...) {
+ color: @first;
+ text-shadow: @rest;
+ }
+
+ span {
+ .hello(red, 1px, 1px, 0px, white);
+ }
+
+ ```
+
+Another way of controlling whether a mixin matches is by specifying a value in
+place of an argument name when declaring the mixin:
+
+ ```less
+ .style(old, @size) {
+ font: @size serif;
+ }
+
+ .style(new, @size) {
+ font: @size sans-serif;
+ }
+
+ .style(@_, @size) {
+ letter-spacing: floor(@size / 6px);
+ }
+
+ em {
+ @switch: old;
+ .style(@switch, 15px);
+ }
+ ```
+
+Notice that two of the three mixins were matched. The mixin with a matching
+first argument, and the generic mixin that matches two arguments. It's common
+to use `@_` as the name of a variable we intend to not use. It has no special
+meaning to LESS, just to the reader of the code.
+
+#### Guards
+
+Another way of restricting when a mixin is mixed in is by using guards. A guard
+is a special expression that is associated with a mixin declaration that is
+evaluated during the mixin process. It must evaluate to true before the mixin
+can be used.
+
+We use the `when` keyword to begin describing a list of guard expressions.
+
+Here's a simple example:
+
+ ```less
+ .guarded(@arg) when (@arg = hello) {
+ color: blue;
+ }
+
+ div {
+ .guarded(hello); // match
+ }
+
+ span {
+ .guarded(world); // no match
+ }
+ ```
+Only the `div`'s mixin will match in this case, because the guard expression
+requires that `@arg` is equal to `hello`.
+
+We can include many different guard expressions by separating them by commas.
+Only one of them needs to match to trigger the mixin:
+
+ ```less
+ .x(@a, @b) when (@a = hello), (@b = world) {
+ width: 960px;
+ }
+
+ div {
+ .x(hello, bar); // match
+ }
+
+ span {
+ .x(foo, world); // match
+ }
+
+ pre {
+ .x(foo, bar); // no match
+ }
+ ```
+
+Instead of a comma, we can use `and` keyword to make it so all of the guards
+must match in order to trigger the mixin. `and` has higher precedence than the
+comma.
+
+ ```less
+ .y(@a, @b) when (@a = hello) and (@b = world) {
+ height: 600px;
+ }
+
+ div {
+ .y(hello, world); // match
+ }
+
+ span {
+ .y(hello, bar); // no match
+ }
+ ```
+
+Commas and `and`s can be mixed and matched.
+
+You can also negate a guard expression by using `not` in from of the parentheses:
+
+ ```less
+ .x(@a) when not (@a = hello) {
+ color: blue;
+ }
+
+ div {
+ .x(hello); // no match
+ }
+ ```
+
+The `=` operator is used to check equality between any two values. For numbers
+the following comparison operators are also defined:
+
+`<`, `>`, `=<`, `>=`
+
+There is also a collection of predicate functions that can be used to test the
+type of a value.
+
+These are `isnumber`, `iscolor`, `iskeyword`, `isstring`, `ispixel`,
+`ispercentage` and `isem`.
+
+ ```less
+ .mix(@a) when (ispercentage(@a)) {
+ height: 500px * @a;
+ }
+ .mix(@a) when (ispixel(@a)) {
+ height: @a;
+ }
+
+ div.a {
+ .mix(50%);
+ }
+
+ div.a {
+ .mix(350px);
+ }
+ ```
+
+#### !important
+
+If you want to apply the `!important` suffix to every property when mixing in a
+mixin, just append `!important` to the end of the call to the mixin:
+
+ ```less
+ .make_bright {
+ color: red;
+ font-weight: bold;
+ }
+
+ .color {
+ color: green;
+ }
+
+ body {
+ .make_bright() !important;
+ .color();
+ }
+
+ ```
+
+### Selector Expressions
+
+Sometimes we want to dynamically generate the selector of a block based on some
+variable or expression. We can do this by using *selector expressions*. Selector
+expressions are CSS selectors that are evaluated in the current scope before
+being written out.
+
+A simple example is a mixin that dynamically creates a selector named after the
+mixin's argument:
+
+ ```less
+ .create-selector(@name) {
+ @{name} {
+ color: red;
+ }
+ }
+
+ .create-selector(hello);
+ .create-selector(world);
+ ```
+
+The string interpolation syntax works inside of selectors, letting you insert varaibles.
+
+Here's an interesting example adapted from Twitter Bootstrap. A couple advanced
+things are going on. We are using [Guards](#guards) along with a recursive
+mixin to work like a loop to generate a series of CSS blocks.
+
+
+ ```less
+ // create our recursive mixin:
+ .spanX (@index) when (@index > 0) {
+ .span@{index} {
+ width: @index * 100px;
+ }
+ .spanX(@index - 1);
+ }
+ .spanX (0) {}
+
+ // mix it into the global scopee:
+ .spanX(4);
+ ```
+
+### Import
+
+Multiple LESS files can be compiled into a single CSS file by using the
+`@import` statement. Be careful, the LESS import statement shares syntax with
+the CSS import statement. If the file being imported ends in a `.less`
+extension, or no extension, then it is treated as a LESS import. Otherwise it
+is left alone and outputted directly:
+
+ ```lessbasic
+ // my_file.less
+ .some-mixin(@height) {
+ height: @height;
+ }
+
+ // main.less
+ @import "main.less" // will import the file if it can be found
+ @import "main.css" // will be left alone
+
+ body {
+ .some-mixin(400px);
+ }
+ ```
+
+All of the following lines are valid ways to import the same file:
+
+ ```lessbasic
+ @import "file";
+ @import 'file.less';
+ @import url("file");
+ @import url('file');
+ @import url(file);
+ ```
+
+When importing, the `importDir` is searched for files. This can be configured,
+see [PHP Interface](#php_interface).
+
+A file is only imported once. If you try to include the same file multiple
+times all the import statements after the first produce no output.
+
+### String Interpolation
+
+String interpolation is a convenient way to insert the value of a variable
+right into a string literal. Given some variable named `@var_name`, you just
+need to write it as `@{var_name}` from within the string to have its value
+inserted:
+
+ ```less
+ @symbol: ">";
+ h1:before {
+ content: "@{symbol}: ";
+ }
+
+ h2:before {
+ content: "@{symbol}@{symbol}: ";
+ }
+ ```
+
+There are two kinds of strings, implicit and explicit strings. Explicit strings
+are wrapped by double quotes, `"hello I am a string"`, or single quotes `'I am
+another string'`. Implicit strings only appear when using `url()`. The text
+between the parentheses is considered a string and thus string interpolation is
+possible:
+
+ ```less
+ @path: "files/";
+ body {
+ background: url(@{path}my_background.png);
+ }
+ ```
+
+### String Format Function
+
+The `%` function can be used to insert values into strings using a *format
+string*. It works similar to `printf` seen in other languages. It has the
+same purpose as string interpolation above, but gives explicit control over
+the output format.
+
+ ```less
+ @symbol: ">";
+ h1:before {
+ content: %("%s: ", @symbol);
+ }
+ ```
+
+The `%` function takes as its first argument the format string, following any
+number of addition arguments that are inserted in place of the format
+directives.
+
+A format directive starts with a `%` and is followed by a single character that
+is either `a`, `d`, or `s`:
+
+ ```less
+ strings: %("%a %d %s %a", hi, 1, 'ok', 'cool');
+ ```
+
+`%a` and `%d` format the value the same way: they compile the argument to its
+CSS value and insert it directly. When used with a string, the quotes are
+included in the output. This typically isn't what we want, so we have the `%s`
+format directive which strips quotes from strings before inserting them.
+
+The `%d` directive functions the same as `%a`, but is typically used for numbers
+assuming the output format of numbers might change in the future.
+
+### String Unquoting
+
+Sometimes you will need to write proprietary CSS syntax that is unable to be
+parsed. As a workaround you can place the code into a string and unquote it.
+Unquoting is the process of outputting a string without its surrounding quotes.
+There are two ways to unquote a string.
+
+The `~` operator in front of a string will unquote that string:
+
+ ```less
+ .class {
+ // a made up, but problematic vendor specific CSS
+ filter: ~"Microsoft.AlphaImage(src='image.png')";
+ }
+ ```
+
+If you are working with other types, such as variables, there is a built in
+function that let's you unquote any value. It is called `e`.
+
+ ```less
+ @color: "red";
+ .class {
+ color: e(@color);
+ }
+ ```
+
+### Built In Functions
+
+**lessphp** has a collection of built in functions:
+
+* `e(str)` -- returns a string without the surrounding quotes.
+ See [String Unquoting](#string_unquoting)
+
+* `floor(number)` -- returns the floor of a numerical input
+* `round(number, [precision])` -- returns the rounded value of numerical input with optional precision
+
+* `lighten(color, percent)` -- lightens `color` by `percent` and returns it
+* `darken(color, percent)` -- darkens `color` by `percent` and returns it
+
+* `saturate(color, percent)` -- saturates `color` by `percent` and returns it
+* `desaturate(color, percent)` -- desaturates `color` by `percent` and returns it
+
+* `fadein(color, percent)` -- makes `color` less transparent by `percent` and returns it
+* `fadeout(color, percent)` -- makes `color` more transparent by `percent` and returns it
+
+* `spin(color, amount)` -- returns a color with `amount` degrees added to hue
+
+* `fade(color, amount)` -- returns a color with the alpha set to `amount`
+
+* `hue(color)` -- returns the hue of `color`
+
+* `saturation(color)` -- returns the saturation of `color`
+
+* `lightness(color)` -- returns the lightness of `color`
+
+* `alpha(color)` -- returns the alpha value of `color` or 1.0 if it doesn't have an alpha
+
+* `percentage(number)` -- converts a floating point number to a percentage, e.g. `0.65` -> `65%`
+
+* `mix(color1, color1, percent)` -- mixes two colors by percentage where 100%
+ keeps all of `color1`, and 0% keeps all of `color2`. Will take into account
+ the alpha of the colors if it exists. See
+ <http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method>.
+
+* `contrast(color, dark, light)` -- if `color` has a lightness value greater
+ than 50% then `dark` is returned, otherwise return `light`.
+
+* `extract(list, index)` -- returns the `index`th item from `list`. The list is
+ `1` indexed, meaning the first item's index is 1, the second is 2, and etc.
+
+* `pow(base, exp)` -- returns `base` raised to the power of `exp`
+
+* `pi()` -- returns pi
+
+* `mod(a,b)` -- returns `a` modulus `b`
+
+* `tan(a)` -- returns tangent of `a` where `a` is in radians
+
+* `cos(a)` -- returns cosine of `a` where `a` is in radians
+
+* `sin(a)` -- returns sine of `a` where `a` is in radians
+
+* `atan(a)` -- returns arc tangent of `a`
+
+* `acos(a)` -- returns arc cosine of `a`
+
+* `asin(a)` -- returns arc sine of `a`
+
+* `sqrt(a)` -- returns square root of `a`
+
+* `rgbahex(color)` -- returns a string containing 4 part hex color.
+
+ This is used to convert a CSS color into the hex format that IE's filter
+ method expects when working with an alpha component.
+
+ ```less
+ .class {
+ @start: rgbahex(rgba(25, 34, 23, .5));
+ @end: rgbahex(rgba(85, 74, 103, .6));
+ // abridged example
+ -ms-filter:
+ e("gradient(start=@{start},end=@{end})");
+ }
+ ```
+
+## PHP Interface
+
+When working with **lessphp** from PHP, the typical flow is to create a new
+instance of `lessc`, configure it how you like, then tell it to compile
+something using one built in compile methods.
+
+Methods:
+
+* [`compile($string)`](#compiling[) -- Compile a string
+
+* [`compileFile($inFile, [$outFile])`](#compiling) -- Compile a file to another or return it
+
+* [`checkedCompile($inFile, $outFile)`](#compiling) -- Compile a file only if it's newer
+
+* [`cachedCompile($cacheOrFile, [$force])`](#compiling_automatically) -- Conditionally compile while tracking imports
+
+* [`setFormatter($formatterName)`](#output_formatting) -- Change how CSS output looks
+
+* [`setPreserveComments($keepComments)`](#preserving_comments) -- Change if comments are kept in output
+
+* [`registerFunction($name, $callable)`](#custom_functions) -- Add a custom function
+
+* [`unregisterFunction($name)`](#custom_functions) -- Remove a registered function
+
+* [`setVariables($vars)`](#setting_variables_from_php) -- Set a variable from PHP
+
+* [`unsetVariable($name)`](#setting_variables_from_php) -- Remove a PHP variable
+
+* [`setImportDir($dirs)`](#import_directory) -- Set the search path for imports
+
+* [`addImportDir($dir)`](#import_directory) -- Append directory to search path for imports
+
+
+### Compiling
+
+The `compile` method compiles a string of LESS code to CSS.
+
+ ```php
+ <?php
+ require "lessc.inc.php";
+
+ $less = new lessc;
+ echo $less->compile(".block { padding: 3 + 4px }");
+ ```
+
+The `compileFile` method reads and compiles a file. It will either return the
+result or write it to the path specified by an optional second argument.
+
+ ```php
+ echo $less->compileFile("input.less");
+ ```
+
+The `compileChecked` method is like `compileFile`, but it only compiles if the output
+file doesn't exist or it's older than the input file:
+
+ ```php
+ $less->checkedCompile("input.less", "output.css");
+ ```
+
+See [Compiling Automatically](#compiling_automatically) for a description of
+the more advanced `cachedCompile` method.
+
+### Output Formatting
+
+Output formatting controls the indentation of the output CSS. Besides the
+default formatter, two additional ones are included and it's also easy to make
+your own.
+
+To use a formatter, the method `setFormatter` is used. Just
+pass the name of the formatter:
+
+ ```php
+ $less = new lessc;
+
+ $less->setFormatter("compressed");
+ echo $less->compile("div { color: lighten(blue, 10%) }");
+ ```
+
+In this example, the `compressed` formatter is used. The formatters are:
+
+ * `lessjs` *(default)* -- Same style used in LESS for JavaScript
+
+ * `compressed` -- Compresses all the unrequired whitespace
+
+ * `classic` -- **lessphp**'s original formatter
+
+To revert to the default formatter, call `setFormatter` with a value of `null`.
+
+#### Custom Formatter
+
+The easiest way to customize the formatter is to create your own instance of an
+existing formatter and alter its public properties before passing it off to
+**lessphp**. The `setFormatter` method can also take an instance of a
+formatter.
+
+Each of the formatter names corresponds to a class with `lessc_formatter_`
+prepended in front of it. Here the classic formatter is customized to use tabs
+instead of spaces:
+
+
+ ```php
+ $formatter = new lessc_formatter_classic;
+ $formatter->indentChar = "\t";
+
+ $less = new lessc;
+ $less->setFormatter($formatter);
+ echo $less->compileFile("myfile.less");
+ ```
+
+For more information about what can be configured with the formatter consult
+the source code.
+
+### Preserving Comments
+
+By default, all comments in the source LESS file are stripped when compiling.
+You might want to keep the `/* */` comments in the output though. For
+example, bundling a license in the file.
+
+Enable or disable comment preservation by calling `setPreserveComments`:
+
+ ```php
+ $less = new lessc;
+ $less->setPreserveComments(true);
+ echo $less->compile("/* hello! */");
+ ```
+
+Comments are disabled by default because there is additional overhead, and more
+often than not they aren't needed.
+
+
+### Compiling Automatically
+
+Often, you want to only compile a LESS file only if it has been modified since
+last compile. This is very important because compiling is performance intensive
+and you should avoid a recompile if it possible.
+
+The `checkedCompile` compile method will do just that. It will check if the
+input file is newer than the output file, or if the output file doesn't exist
+yet, and compile only then.
+
+ ```php
+ $less->checkedCompile("input.less", "output.css");
+ ```
+
+There's a problem though. `checkedCompile` is very basic, it only checks the
+input file's modification time. It is unaware of any files from `@import`.
+
+
+For this reason we also have `cachedCompile`. It's slightly more complex, but
+gives us the ability to check changes to all files including those imported. It
+takes one argument, either the name of the file we want to compile, or an
+existing *cache object*. Its return value is an updated cache object.
+
+If we don't have a cache object, then we call the function with the name of the
+file to get the initial cache object. If we do have a cache object, then we
+call the function with it. In both cases, an updated cache object is returned.
+
+The cache object keeps track of all the files that must be checked in order to
+determine if a rebuild is required.
+
+The cache object is a plain PHP `array`. It stores the last time it compiled in
+`$cache["updated"]` and output of the compile in `$cache["compiled"]`.
+
+Here we demonstrate creating an new cache object, then using it to see if we
+have a recompiled version available to be written:
+
+
+ ```php
+ $inputFile = "myfile.less";
+ $outputFile = "myfile.css";
+
+ $less = new lessc;
+
+ // create a new cache object, and compile
+ $cache = $less->cachedCompile($inputFile);
+
+ file_put_contents($outputFile, $cache["compiled"]);
+
+ // the next time we run, write only if it has updated
+ $last_updated = $cache["updated"];
+ $cache = $less->cachedCompile($cache);
+ if ($cache["updated"] > $last_updated) {
+ file_put_contents($outputFile, $cache["compiled"]);
+ }
+
+ ```
+
+In order for the system to fully work, we must save cache object between
+requests. Because it's a plain PHP `array`, it's sufficient to
+[`serialize`](http://php.net/serialize) it and save it the string somewhere
+like a file or in persistent memory.
+
+An example with saving cache object to a file:
+
+ ```php
+ function autoCompileLess($inputFile, $outputFile) {
+ // load the cache
+ $cacheFile = $inputFile.".cache";
+
+ if (file_exists($cacheFile)) {
+ $cache = unserialize(file_get_contents($cacheFile));
+ } else {
+ $cache = $inputFile;
+ }
+
+ $less = new lessc;
+ $newCache = $less->cachedCompile($cache);
+
+ if (!is_array($cache) || $newCache["updated"] > $cache["updated"]) {
+ file_put_contents($cacheFile, serialize($newCache));
+ file_put_contents($outputFile, $newCache['compiled']);
+ }
+ }
+
+ autoCompileLess('myfile.less', 'myfile.css');
+ ```
+
+`cachedCompile` method takes an optional second argument, `$force`. Passing in
+true will cause the input to always be recompiled.
+
+### Error Handling
+
+All of the compile methods will throw an `Exception` if the parsing fails or
+there is a compile time error. Compile time errors include things like passing
+incorrectly typed values for functions that expect specific things, like the
+color manipulation functions.
+
+ ```php
+ $less = new lessc;
+ try {
+ $less->compile("} invalid LESS }}}");
+ } catch (Exception $ex) {
+ echo "lessphp fatal error: ".$ex->getMessage();
+ }
+ ```
+### Setting Variables From PHP
+
+Before compiling any code you can set initial LESS variables from PHP. The
+`setVariables` method lets us do this. It takes an associative array of names
+to values. The values must be strings, and will be parsed into correct CSS
+values.
+
+
+ ```php
+ $less = new lessc;
+
+ $less->setVariables(array(
+ "color" => "red",
+ "base" => "960px"
+ ));
+
+ echo $less->compile(".magic { color: @color; width: @base - 200; }");
+ ```
+
+If you need to unset a variable, the `unsetVariable` method is available. It
+takes the name of the variable to unset.
+
+ ```php
+ $less->unsetVariable("color");
+ ```
+
+Be aware that the value of the variable is a string containing a CSS value. So
+if you want to pass a LESS string in, you're going to need two sets of quotes.
+One for PHP and one for LESS.
+
+
+ ```php
+ $less->setVariables(array(
+ "url" => "'http://example.com.com/'"
+ ));
+
+ echo $less->compile("body { background: url("@{url}/bg.png"); }");
+ ```
+
+### Import Directory
+
+When running the `@import` directive, an array of directories called the import
+search path is searched through to find the file being asked for.
+
+By default, when using `compile`, the import search path just contains `""`,
+which is equivalent to the current directory of the script. If `compileFile` is
+used, then the directory of the file being compiled is used as the starting
+import search path.
+
+Two methods are available for configuring the search path.
+
+`setImportDir` will overwrite the search path with its argument. If the value
+isn't an array it will be converted to one.
+
+
+In this example, `@import "colors";` will look for either
+`assets/less/colors.less` or `assets/bootstrap/colors.less` in that order:
+
+ ```php
+ $less->setImportDir(array("assets/less/", "assets/bootstrap"));
+
+ echo $less->compile('@import "colors";');
+ ```
+
+`addImportDir` will append a single path to the import search path instead of
+overwriting the whole thing.
+
+ ```php
+ $less->addImportDir("public/stylesheets");
+ ```
+
+### Custom Functions
+
+**lessphp** has a simple extension interface where you can implement user
+functions that will be exposed in LESS code during the compile. They can be a
+little tricky though because you need to work with the **lessphp** type system.
+
+The two methods we are interested in are `registerFunction` and
+`unregisterFunction`. `registerFunction` takes two arguments, a name and a
+callable value. `unregisterFunction` just takes the name of an existing
+function to remove.
+
+Here's an example that adds a function called `double` that doubles any numeric
+argument:
+
+ ```php
+ <?php
+ include "lessc.inc.php";
+
+ function lessphp_double($arg) {
+ list($type, $value) = $arg;
+ return array($type, $value*2);
+ }
+
+ $less = new lessc;
+ $less->registerFunction("double", "lessphp_double");
+
+ // gives us a width of 800px
+ echo $less->compile("div { width: double(400px); }");
+ ```
+
+The second argument to `registerFunction` is any *callable value* that is
+understood by [`call_user_func`](http://php.net/call_user_func).
+
+If we are using PHP 5.3 or above then we are free to pass a function literal
+like so:
+
+ ```php
+ $less->registerFunction("double", function($arg) {
+ list($type, $value, $unit) = $arg;
+ return array($type, $value*2, $unit);
+ });
+ ```
+
+Now let's talk about the `double` function itself.
+
+Although a little verbose, the implementation gives us some insight on the type
+system. All values in **lessphp** are stored in an array where the 0th element
+is a string representing the type, and the other elements make up the
+associated data for that value.
+
+The best way to get an understanding of the system is to register is dummy
+function which does a `var_dump` on the argument. Try passing the function
+different values from LESS and see what the results are.
+
+The return value of the registered function must also be a **lessphp** type,
+but if it is a string or numeric value, it will automatically be coerced into
+an appropriate typed value. In our example, we reconstruct the value with our
+modifications while making sure that we preserve the original type.
+
+The instance of **lessphp** itself is sent to the registered function as the
+second argument in addition to the arguments array.
+
+## Command Line Interface
+
+**lessphp** comes with a command line script written in PHP that can be used to
+invoke the compiler from the terminal. On Linux and OSX, all you need to do is
+place `plessc` and `lessc.inc.php` somewhere in your PATH (or you can run it in
+the current directory as well). On windows you'll need a copy of `php.exe` to
+run the file. To compile a file, `input.less` to CSS, run:
+
+ ```bash
+ $ plessc input.less
+ ```
+
+To write to a file, redirect standard out:
+
+ ```bash
+ $ plessc input.less > output.css
+ ```
+
+To compile code directly on the command line:
+
+ ```bash
+ $ plessc -r "@color: red; body { color: @color; }"
+ ```
+
+To watch a file for changes, and compile it as needed, use the `-w` flag:
+
+ ```bash
+ $ plessc -w input-file output-file
+ ```
+
+Errors from watch mode are written to standard out.
+
+
+## License
+
+Copyright (c) 2012 Leaf Corcoran, <http://leafo.net/lessphp>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+*Also under GPL3 if required, see `LICENSE` file*
+
diff --git a/vendor/leafo/lessphp/lessc.inc.php b/vendor/leafo/lessphp/lessc.inc.php
new file mode 100644
index 00000000..2292f219
--- /dev/null
+++ b/vendor/leafo/lessphp/lessc.inc.php
@@ -0,0 +1,3768 @@
+<?php
+
+/**
+ * lessphp v0.5.0
+ * http://leafo.net/lessphp
+ *
+ * LESS CSS compiler, adapted from http://lesscss.org
+ *
+ * Copyright 2013, Leaf Corcoran <leafot@gmail.com>
+ * Licensed under MIT or GPLv3, see LICENSE
+ */
+
+
+/**
+ * The LESS compiler and parser.
+ *
+ * Converting LESS to CSS is a three stage process. The incoming file is parsed
+ * by `lessc_parser` into a syntax tree, then it is compiled into another tree
+ * representing the CSS structure by `lessc`. The CSS tree is fed into a
+ * formatter, like `lessc_formatter` which then outputs CSS as a string.
+ *
+ * During the first compile, all values are *reduced*, which means that their
+ * types are brought to the lowest form before being dump as strings. This
+ * handles math equations, variable dereferences, and the like.
+ *
+ * The `parse` function of `lessc` is the entry point.
+ *
+ * In summary:
+ *
+ * The `lessc` class creates an instance of the parser, feeds it LESS code,
+ * then transforms the resulting tree to a CSS tree. This class also holds the
+ * evaluation context, such as all available mixins and variables at any given
+ * time.
+ *
+ * The `lessc_parser` class is only concerned with parsing its input.
+ *
+ * The `lessc_formatter` takes a CSS tree, and dumps it to a formatted string,
+ * handling things like indentation.
+ */
+class lessc {
+ static public $VERSION = "v0.5.0";
+
+ static public $TRUE = array("keyword", "true");
+ static public $FALSE = array("keyword", "false");
+
+ protected $libFunctions = array();
+ protected $registeredVars = array();
+ protected $preserveComments = false;
+
+ public $vPrefix = '@'; // prefix of abstract properties
+ public $mPrefix = '$'; // prefix of abstract blocks
+ public $parentSelector = '&';
+
+ public $importDisabled = false;
+ public $importDir = '';
+
+ protected $numberPrecision = null;
+
+ protected $allParsedFiles = array();
+
+ // set to the parser that generated the current line when compiling
+ // so we know how to create error messages
+ protected $sourceParser = null;
+ protected $sourceLoc = null;
+
+ static protected $nextImportId = 0; // uniquely identify imports
+
+ // attempts to find the path of an import url, returns null for css files
+ protected function findImport($url) {
+ foreach ((array)$this->importDir as $dir) {
+ $full = $dir.(substr($dir, -1) != '/' ? '/' : '').$url;
+ if ($this->fileExists($file = $full.'.less') || $this->fileExists($file = $full)) {
+ return $file;
+ }
+ }
+
+ return null;
+ }
+
+ protected function fileExists($name) {
+ return is_file($name);
+ }
+
+ static public function compressList($items, $delim) {
+ if (!isset($items[1]) && isset($items[0])) return $items[0];
+ else return array('list', $delim, $items);
+ }
+
+ static public function preg_quote($what) {
+ return preg_quote($what, '/');
+ }
+
+ protected function tryImport($importPath, $parentBlock, $out) {
+ if ($importPath[0] == "function" && $importPath[1] == "url") {
+ $importPath = $this->flattenList($importPath[2]);
+ }
+
+ $str = $this->coerceString($importPath);
+ if ($str === null) return false;
+
+ $url = $this->compileValue($this->lib_e($str));
+
+ // don't import if it ends in css
+ if (substr_compare($url, '.css', -4, 4) === 0) return false;
+
+ $realPath = $this->findImport($url);
+
+ if ($realPath === null) return false;
+
+ if ($this->importDisabled) {
+ return array(false, "/* import disabled */");
+ }
+
+ if (isset($this->allParsedFiles[realpath($realPath)])) {
+ return array(false, null);
+ }
+
+ $this->addParsedFile($realPath);
+ $parser = $this->makeParser($realPath);
+ $root = $parser->parse(file_get_contents($realPath));
+
+ // set the parents of all the block props
+ foreach ($root->props as $prop) {
+ if ($prop[0] == "block") {
+ $prop[1]->parent = $parentBlock;
+ }
+ }
+
+ // copy mixins into scope, set their parents
+ // bring blocks from import into current block
+ // TODO: need to mark the source parser these came from this file
+ foreach ($root->children as $childName => $child) {
+ if (isset($parentBlock->children[$childName])) {
+ $parentBlock->children[$childName] = array_merge(
+ $parentBlock->children[$childName],
+ $child);
+ } else {
+ $parentBlock->children[$childName] = $child;
+ }
+ }
+
+ $pi = pathinfo($realPath);
+ $dir = $pi["dirname"];
+
+ list($top, $bottom) = $this->sortProps($root->props, true);
+ $this->compileImportedProps($top, $parentBlock, $out, $parser, $dir);
+
+ return array(true, $bottom, $parser, $dir);
+ }
+
+ protected function compileImportedProps($props, $block, $out, $sourceParser, $importDir) {
+ $oldSourceParser = $this->sourceParser;
+
+ $oldImport = $this->importDir;
+
+ // TODO: this is because the importDir api is stupid
+ $this->importDir = (array)$this->importDir;
+ array_unshift($this->importDir, $importDir);
+
+ foreach ($props as $prop) {
+ $this->compileProp($prop, $block, $out);
+ }
+
+ $this->importDir = $oldImport;
+ $this->sourceParser = $oldSourceParser;
+ }
+
+ /**
+ * Recursively compiles a block.
+ *
+ * A block is analogous to a CSS block in most cases. A single LESS document
+ * is encapsulated in a block when parsed, but it does not have parent tags
+ * so all of it's children appear on the root level when compiled.
+ *
+ * Blocks are made up of props and children.
+ *
+ * Props are property instructions, array tuples which describe an action
+ * to be taken, eg. write a property, set a variable, mixin a block.
+ *
+ * The children of a block are just all the blocks that are defined within.
+ * This is used to look up mixins when performing a mixin.
+ *
+ * Compiling the block involves pushing a fresh environment on the stack,
+ * and iterating through the props, compiling each one.
+ *
+ * See lessc::compileProp()
+ *
+ */
+ protected function compileBlock($block) {
+ switch ($block->type) {
+ case "root":
+ $this->compileRoot($block);
+ break;
+ case null:
+ $this->compileCSSBlock($block);
+ break;
+ case "media":
+ $this->compileMedia($block);
+ break;
+ case "directive":
+ $name = "@" . $block->name;
+ if (!empty($block->value)) {
+ $name .= " " . $this->compileValue($this->reduce($block->value));
+ }
+
+ $this->compileNestedBlock($block, array($name));
+ break;
+ default:
+ $this->throwError("unknown block type: $block->type\n");
+ }
+ }
+
+ protected function compileCSSBlock($block) {
+ $env = $this->pushEnv();
+
+ $selectors = $this->compileSelectors($block->tags);
+ $env->selectors = $this->multiplySelectors($selectors);
+ $out = $this->makeOutputBlock(null, $env->selectors);
+
+ $this->scope->children[] = $out;
+ $this->compileProps($block, $out);
+
+ $block->scope = $env; // mixins carry scope with them!
+ $this->popEnv();
+ }
+
+ protected function compileMedia($media) {
+ $env = $this->pushEnv($media);
+ $parentScope = $this->mediaParent($this->scope);
+
+ $query = $this->compileMediaQuery($this->multiplyMedia($env));
+
+ $this->scope = $this->makeOutputBlock($media->type, array($query));
+ $parentScope->children[] = $this->scope;
+
+ $this->compileProps($media, $this->scope);
+
+ if (count($this->scope->lines) > 0) {
+ $orphanSelelectors = $this->findClosestSelectors();
+ if (!is_null($orphanSelelectors)) {
+ $orphan = $this->makeOutputBlock(null, $orphanSelelectors);
+ $orphan->lines = $this->scope->lines;
+ array_unshift($this->scope->children, $orphan);
+ $this->scope->lines = array();
+ }
+ }
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ protected function mediaParent($scope) {
+ while (!empty($scope->parent)) {
+ if (!empty($scope->type) && $scope->type != "media") {
+ break;
+ }
+ $scope = $scope->parent;
+ }
+
+ return $scope;
+ }
+
+ protected function compileNestedBlock($block, $selectors) {
+ $this->pushEnv($block);
+ $this->scope = $this->makeOutputBlock($block->type, $selectors);
+ $this->scope->parent->children[] = $this->scope;
+
+ $this->compileProps($block, $this->scope);
+
+ $this->scope = $this->scope->parent;
+ $this->popEnv();
+ }
+
+ protected function compileRoot($root) {
+ $this->pushEnv();
+ $this->scope = $this->makeOutputBlock($root->type);
+ $this->compileProps($root, $this->scope);
+ $this->popEnv();
+ }
+
+ protected function compileProps($block, $out) {
+ foreach ($this->sortProps($block->props) as $prop) {
+ $this->compileProp($prop, $block, $out);
+ }
+ $out->lines = $this->deduplicate($out->lines);
+ }
+
+ /**
+ * Deduplicate lines in a block. Comments are not deduplicated. If a
+ * duplicate rule is detected, the comments immediately preceding each
+ * occurence are consolidated.
+ */
+ protected function deduplicate($lines) {
+ $unique = array();
+ $comments = array();
+
+ foreach($lines as $line) {
+ if (strpos($line, '/*') === 0) {
+ $comments[] = $line;
+ continue;
+ }
+ if (!in_array($line, $unique)) {
+ $unique[] = $line;
+ }
+ array_splice($unique, array_search($line, $unique), 0, $comments);
+ $comments = array();
+ }
+ return array_merge($unique, $comments);
+ }
+
+ protected function sortProps($props, $split = false) {
+ $vars = array();
+ $imports = array();
+ $other = array();
+ $stack = array();
+
+ foreach ($props as $prop) {
+ switch ($prop[0]) {
+ case "comment":
+ $stack[] = $prop;
+ break;
+ case "assign":
+ $stack[] = $prop;
+ if (isset($prop[1][0]) && $prop[1][0] == $this->vPrefix) {
+ $vars = array_merge($vars, $stack);
+ } else {
+ $other = array_merge($other, $stack);
+ }
+ $stack = array();
+ break;
+ case "import":
+ $id = self::$nextImportId++;
+ $prop[] = $id;
+ $stack[] = $prop;
+ $imports = array_merge($imports, $stack);
+ $other[] = array("import_mixin", $id);
+ $stack = array();
+ break;
+ default:
+ $stack[] = $prop;
+ $other = array_merge($other, $stack);
+ $stack = array();
+ break;
+ }
+ }
+ $other = array_merge($other, $stack);
+
+ if ($split) {
+ return array(array_merge($imports, $vars), $other);
+ } else {
+ return array_merge($imports, $vars, $other);
+ }
+ }
+
+ protected function compileMediaQuery($queries) {
+ $compiledQueries = array();
+ foreach ($queries as $query) {
+ $parts = array();
+ foreach ($query as $q) {
+ switch ($q[0]) {
+ case "mediaType":
+ $parts[] = implode(" ", array_slice($q, 1));
+ break;
+ case "mediaExp":
+ if (isset($q[2])) {
+ $parts[] = "($q[1]: " .
+ $this->compileValue($this->reduce($q[2])) . ")";
+ } else {
+ $parts[] = "($q[1])";
+ }
+ break;
+ case "variable":
+ $parts[] = $this->compileValue($this->reduce($q));
+ break;
+ }
+ }
+
+ if (count($parts) > 0) {
+ $compiledQueries[] = implode(" and ", $parts);
+ }
+ }
+
+ $out = "@media";
+ if (!empty($parts)) {
+ $out .= " " .
+ implode($this->formatter->selectorSeparator, $compiledQueries);
+ }
+ return $out;
+ }
+
+ protected function multiplyMedia($env, $childQueries = null) {
+ if (is_null($env) ||
+ !empty($env->block->type) && $env->block->type != "media")
+ {
+ return $childQueries;
+ }
+
+ // plain old block, skip
+ if (empty($env->block->type)) {
+ return $this->multiplyMedia($env->parent, $childQueries);
+ }
+
+ $out = array();
+ $queries = $env->block->queries;
+ if (is_null($childQueries)) {
+ $out = $queries;
+ } else {
+ foreach ($queries as $parent) {
+ foreach ($childQueries as $child) {
+ $out[] = array_merge($parent, $child);
+ }
+ }
+ }
+
+ return $this->multiplyMedia($env->parent, $out);
+ }
+
+ protected function expandParentSelectors(&$tag, $replace) {
+ $parts = explode("$&$", $tag);
+ $count = 0;
+ foreach ($parts as &$part) {
+ $part = str_replace($this->parentSelector, $replace, $part, $c);
+ $count += $c;
+ }
+ $tag = implode($this->parentSelector, $parts);
+ return $count;
+ }
+
+ protected function findClosestSelectors() {
+ $env = $this->env;
+ $selectors = null;
+ while ($env !== null) {
+ if (isset($env->selectors)) {
+ $selectors = $env->selectors;
+ break;
+ }
+ $env = $env->parent;
+ }
+
+ return $selectors;
+ }
+
+
+ // multiply $selectors against the nearest selectors in env
+ protected function multiplySelectors($selectors) {
+ // find parent selectors
+
+ $parentSelectors = $this->findClosestSelectors();
+ if (is_null($parentSelectors)) {
+ // kill parent reference in top level selector
+ foreach ($selectors as &$s) {
+ $this->expandParentSelectors($s, "");
+ }
+
+ return $selectors;
+ }
+
+ $out = array();
+ foreach ($parentSelectors as $parent) {
+ foreach ($selectors as $child) {
+ $count = $this->expandParentSelectors($child, $parent);
+
+ // don't prepend the parent tag if & was used
+ if ($count > 0) {
+ $out[] = trim($child);
+ } else {
+ $out[] = trim($parent . ' ' . $child);
+ }
+ }
+ }
+
+ return $out;
+ }
+
+ // reduces selector expressions
+ protected function compileSelectors($selectors) {
+ $out = array();
+
+ foreach ($selectors as $s) {
+ if (is_array($s)) {
+ list(, $value) = $s;
+ $out[] = trim($this->compileValue($this->reduce($value)));
+ } else {
+ $out[] = $s;
+ }
+ }
+
+ return $out;
+ }
+
+ protected function eq($left, $right) {
+ return $left == $right;
+ }
+
+ protected function patternMatch($block, $orderedArgs, $keywordArgs) {
+ // match the guards if it has them
+ // any one of the groups must have all its guards pass for a match
+ if (!empty($block->guards)) {
+ $groupPassed = false;
+ foreach ($block->guards as $guardGroup) {
+ foreach ($guardGroup as $guard) {
+ $this->pushEnv();
+ $this->zipSetArgs($block->args, $orderedArgs, $keywordArgs);
+
+ $negate = false;
+ if ($guard[0] == "negate") {
+ $guard = $guard[1];
+ $negate = true;
+ }
+
+ $passed = $this->reduce($guard) == self::$TRUE;
+ if ($negate) $passed = !$passed;
+
+ $this->popEnv();
+
+ if ($passed) {
+ $groupPassed = true;
+ } else {
+ $groupPassed = false;
+ break;
+ }
+ }
+
+ if ($groupPassed) break;
+ }
+
+ if (!$groupPassed) {
+ return false;
+ }
+ }
+
+ if (empty($block->args)) {
+ return $block->isVararg || empty($orderedArgs) && empty($keywordArgs);
+ }
+
+ $remainingArgs = $block->args;
+ if ($keywordArgs) {
+ $remainingArgs = array();
+ foreach ($block->args as $arg) {
+ if ($arg[0] == "arg" && isset($keywordArgs[$arg[1]])) {
+ continue;
+ }
+
+ $remainingArgs[] = $arg;
+ }
+ }
+
+ $i = -1; // no args
+ // try to match by arity or by argument literal
+ foreach ($remainingArgs as $i => $arg) {
+ switch ($arg[0]) {
+ case "lit":
+ if (empty($orderedArgs[$i]) || !$this->eq($arg[1], $orderedArgs[$i])) {
+ return false;
+ }
+ break;
+ case "arg":
+ // no arg and no default value
+ if (!isset($orderedArgs[$i]) && !isset($arg[2])) {
+ return false;
+ }
+ break;
+ case "rest":
+ $i--; // rest can be empty
+ break 2;
+ }
+ }
+
+ if ($block->isVararg) {
+ return true; // not having enough is handled above
+ } else {
+ $numMatched = $i + 1;
+ // greater than becuase default values always match
+ return $numMatched >= count($orderedArgs);
+ }
+ }
+
+ protected function patternMatchAll($blocks, $orderedArgs, $keywordArgs, $skip=array()) {
+ $matches = null;
+ foreach ($blocks as $block) {
+ // skip seen blocks that don't have arguments
+ if (isset($skip[$block->id]) && !isset($block->args)) {
+ continue;
+ }
+
+ if ($this->patternMatch($block, $orderedArgs, $keywordArgs)) {
+ $matches[] = $block;
+ }
+ }
+
+ return $matches;
+ }
+
+ // attempt to find blocks matched by path and args
+ protected function findBlocks($searchIn, $path, $orderedArgs, $keywordArgs, $seen=array()) {
+ if ($searchIn == null) return null;
+ if (isset($seen[$searchIn->id])) return null;
+ $seen[$searchIn->id] = true;
+
+ $name = $path[0];
+
+ if (isset($searchIn->children[$name])) {
+ $blocks = $searchIn->children[$name];
+ if (count($path) == 1) {
+ $matches = $this->patternMatchAll($blocks, $orderedArgs, $keywordArgs, $seen);
+ if (!empty($matches)) {
+ // This will return all blocks that match in the closest
+ // scope that has any matching block, like lessjs
+ return $matches;
+ }
+ } else {
+ $matches = array();
+ foreach ($blocks as $subBlock) {
+ $subMatches = $this->findBlocks($subBlock,
+ array_slice($path, 1), $orderedArgs, $keywordArgs, $seen);
+
+ if (!is_null($subMatches)) {
+ foreach ($subMatches as $sm) {
+ $matches[] = $sm;
+ }
+ }
+ }
+
+ return count($matches) > 0 ? $matches : null;
+ }
+ }
+ if ($searchIn->parent === $searchIn) return null;
+ return $this->findBlocks($searchIn->parent, $path, $orderedArgs, $keywordArgs, $seen);
+ }
+
+ // sets all argument names in $args to either the default value
+ // or the one passed in through $values
+ protected function zipSetArgs($args, $orderedValues, $keywordValues) {
+ $assignedValues = array();
+
+ $i = 0;
+ foreach ($args as $a) {
+ if ($a[0] == "arg") {
+ if (isset($keywordValues[$a[1]])) {
+ // has keyword arg
+ $value = $keywordValues[$a[1]];
+ } elseif (isset($orderedValues[$i])) {
+ // has ordered arg
+ $value = $orderedValues[$i];
+ $i++;
+ } elseif (isset($a[2])) {
+ // has default value
+ $value = $a[2];
+ } else {
+ $this->throwError("Failed to assign arg " . $a[1]);
+ $value = null; // :(
+ }
+
+ $value = $this->reduce($value);
+ $this->set($a[1], $value);
+ $assignedValues[] = $value;
+ } else {
+ // a lit
+ $i++;
+ }
+ }
+
+ // check for a rest
+ $last = end($args);
+ if ($last[0] == "rest") {
+ $rest = array_slice($orderedValues, count($args) - 1);
+ $this->set($last[1], $this->reduce(array("list", " ", $rest)));
+ }
+
+ // wow is this the only true use of PHP's + operator for arrays?
+ $this->env->arguments = $assignedValues + $orderedValues;
+ }
+
+ // compile a prop and update $lines or $blocks appropriately
+ protected function compileProp($prop, $block, $out) {
+ // set error position context
+ $this->sourceLoc = isset($prop[-1]) ? $prop[-1] : -1;
+
+ switch ($prop[0]) {
+ case 'assign':
+ list(, $name, $value) = $prop;
+ if ($name[0] == $this->vPrefix) {
+ $this->set($name, $value);
+ } else {
+ $out->lines[] = $this->formatter->property($name,
+ $this->compileValue($this->reduce($value)));
+ }
+ break;
+ case 'block':
+ list(, $child) = $prop;
+ $this->compileBlock($child);
+ break;
+ case 'mixin':
+ list(, $path, $args, $suffix) = $prop;
+
+ $orderedArgs = array();
+ $keywordArgs = array();
+ foreach ((array)$args as $arg) {
+ $argval = null;
+ switch ($arg[0]) {
+ case "arg":
+ if (!isset($arg[2])) {
+ $orderedArgs[] = $this->reduce(array("variable", $arg[1]));
+ } else {
+ $keywordArgs[$arg[1]] = $this->reduce($arg[2]);
+ }
+ break;
+
+ case "lit":
+ $orderedArgs[] = $this->reduce($arg[1]);
+ break;
+ default:
+ $this->throwError("Unknown arg type: " . $arg[0]);
+ }
+ }
+
+ $mixins = $this->findBlocks($block, $path, $orderedArgs, $keywordArgs);
+
+ if ($mixins === null) {
+ $this->throwError("{$prop[1][0]} is undefined");
+ }
+
+ foreach ($mixins as $mixin) {
+ if ($mixin === $block && !$orderedArgs) {
+ continue;
+ }
+
+ $haveScope = false;
+ if (isset($mixin->parent->scope)) {
+ $haveScope = true;
+ $mixinParentEnv = $this->pushEnv();
+ $mixinParentEnv->storeParent = $mixin->parent->scope;
+ }
+
+ $haveArgs = false;
+ if (isset($mixin->args)) {
+ $haveArgs = true;
+ $this->pushEnv();
+ $this->zipSetArgs($mixin->args, $orderedArgs, $keywordArgs);
+ }
+
+ $oldParent = $mixin->parent;
+ if ($mixin != $block) $mixin->parent = $block;
+
+ foreach ($this->sortProps($mixin->props) as $subProp) {
+ if ($suffix !== null &&
+ $subProp[0] == "assign" &&
+ is_string($subProp[1]) &&
+ $subProp[1]{0} != $this->vPrefix)
+ {
+ $subProp[2] = array(
+ 'list', ' ',
+ array($subProp[2], array('keyword', $suffix))
+ );
+ }
+
+ $this->compileProp($subProp, $mixin, $out);
+ }
+
+ $mixin->parent = $oldParent;
+
+ if ($haveArgs) $this->popEnv();
+ if ($haveScope) $this->popEnv();
+ }
+
+ break;
+ case 'raw':
+ $out->lines[] = $prop[1];
+ break;
+ case "directive":
+ list(, $name, $value) = $prop;
+ $out->lines[] = "@$name " . $this->compileValue($this->reduce($value)).';';
+ break;
+ case "comment":
+ $out->lines[] = $prop[1];
+ break;
+ case "import";
+ list(, $importPath, $importId) = $prop;
+ $importPath = $this->reduce($importPath);
+
+ if (!isset($this->env->imports)) {
+ $this->env->imports = array();
+ }
+
+ $result = $this->tryImport($importPath, $block, $out);
+
+ $this->env->imports[$importId] = $result === false ?
+ array(false, "@import " . $this->compileValue($importPath).";") :
+ $result;
+
+ break;
+ case "import_mixin":
+ list(,$importId) = $prop;
+ $import = $this->env->imports[$importId];
+ if ($import[0] === false) {
+ if (isset($import[1])) {
+ $out->lines[] = $import[1];
+ }
+ } else {
+ list(, $bottom, $parser, $importDir) = $import;
+ $this->compileImportedProps($bottom, $block, $out, $parser, $importDir);
+ }
+
+ break;
+ default:
+ $this->throwError("unknown op: {$prop[0]}\n");
+ }
+ }
+
+
+ /**
+ * Compiles a primitive value into a CSS property value.
+ *
+ * Values in lessphp are typed by being wrapped in arrays, their format is
+ * typically:
+ *
+ * array(type, contents [, additional_contents]*)
+ *
+ * The input is expected to be reduced. This function will not work on
+ * things like expressions and variables.
+ */
+ public function compileValue($value) {
+ switch ($value[0]) {
+ case 'list':
+ // [1] - delimiter
+ // [2] - array of values
+ return implode($value[1], array_map(array($this, 'compileValue'), $value[2]));
+ case 'raw_color':
+ if (!empty($this->formatter->compressColors)) {
+ return $this->compileValue($this->coerceColor($value));
+ }
+ return $value[1];
+ case 'keyword':
+ // [1] - the keyword
+ return $value[1];
+ case 'number':
+ list(, $num, $unit) = $value;
+ // [1] - the number
+ // [2] - the unit
+ if ($this->numberPrecision !== null) {
+ $num = round($num, $this->numberPrecision);
+ }
+ return $num . $unit;
+ case 'string':
+ // [1] - contents of string (includes quotes)
+ list(, $delim, $content) = $value;
+ foreach ($content as &$part) {
+ if (is_array($part)) {
+ $part = $this->compileValue($part);
+ }
+ }
+ return $delim . implode($content) . $delim;
+ case 'color':
+ // [1] - red component (either number or a %)
+ // [2] - green component
+ // [3] - blue component
+ // [4] - optional alpha component
+ list(, $r, $g, $b) = $value;
+ $r = round($r);
+ $g = round($g);
+ $b = round($b);
+
+ if (count($value) == 5 && $value[4] != 1) { // rgba
+ return 'rgba('.$r.','.$g.','.$b.','.$value[4].')';
+ }
+
+ $h = sprintf("#%02x%02x%02x", $r, $g, $b);
+
+ if (!empty($this->formatter->compressColors)) {
+ // Converting hex color to short notation (e.g. #003399 to #039)
+ if ($h[1] === $h[2] && $h[3] === $h[4] && $h[5] === $h[6]) {
+ $h = '#' . $h[1] . $h[3] . $h[5];
+ }
+ }
+
+ return $h;
+
+ case 'function':
+ list(, $name, $args) = $value;
+ return $name.'('.$this->compileValue($args).')';
+ default: // assumed to be unit
+ $this->throwError("unknown value type: $value[0]");
+ }
+ }
+
+ protected function lib_pow($args) {
+ list($base, $exp) = $this->assertArgs($args, 2, "pow");
+ return pow($this->assertNumber($base), $this->assertNumber($exp));
+ }
+
+ protected function lib_pi() {
+ return pi();
+ }
+
+ protected function lib_mod($args) {
+ list($a, $b) = $this->assertArgs($args, 2, "mod");
+ return $this->assertNumber($a) % $this->assertNumber($b);
+ }
+
+ protected function lib_tan($num) {
+ return tan($this->assertNumber($num));
+ }
+
+ protected function lib_sin($num) {
+ return sin($this->assertNumber($num));
+ }
+
+ protected function lib_cos($num) {
+ return cos($this->assertNumber($num));
+ }
+
+ protected function lib_atan($num) {
+ $num = atan($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_asin($num) {
+ $num = asin($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_acos($num) {
+ $num = acos($this->assertNumber($num));
+ return array("number", $num, "rad");
+ }
+
+ protected function lib_sqrt($num) {
+ return sqrt($this->assertNumber($num));
+ }
+
+ protected function lib_extract($value) {
+ list($list, $idx) = $this->assertArgs($value, 2, "extract");
+ $idx = $this->assertNumber($idx);
+ // 1 indexed
+ if ($list[0] == "list" && isset($list[2][$idx - 1])) {
+ return $list[2][$idx - 1];
+ }
+ }
+
+ protected function lib_isnumber($value) {
+ return $this->toBool($value[0] == "number");
+ }
+
+ protected function lib_isstring($value) {
+ return $this->toBool($value[0] == "string");
+ }
+
+ protected function lib_iscolor($value) {
+ return $this->toBool($this->coerceColor($value));
+ }
+
+ protected function lib_iskeyword($value) {
+ return $this->toBool($value[0] == "keyword");
+ }
+
+ protected function lib_ispixel($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "px");
+ }
+
+ protected function lib_ispercentage($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "%");
+ }
+
+ protected function lib_isem($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "em");
+ }
+
+ protected function lib_isrem($value) {
+ return $this->toBool($value[0] == "number" && $value[2] == "rem");
+ }
+
+ protected function lib_rgbahex($color) {
+ $color = $this->coerceColor($color);
+ if (is_null($color))
+ $this->throwError("color expected for rgbahex");
+
+ return sprintf("#%02x%02x%02x%02x",
+ isset($color[4]) ? $color[4]*255 : 255,
+ $color[1],$color[2], $color[3]);
+ }
+
+ protected function lib_argb($color){
+ return $this->lib_rgbahex($color);
+ }
+
+ /**
+ * Given an url, decide whether to output a regular link or the base64-encoded contents of the file
+ *
+ * @param array $value either an argument list (two strings) or a single string
+ * @return string formatted url(), either as a link or base64-encoded
+ */
+ protected function lib_data_uri($value) {
+ $mime = ($value[0] === 'list') ? $value[2][0][2] : null;
+ $url = ($value[0] === 'list') ? $value[2][1][2][0] : $value[2][0];
+
+ $fullpath = $this->findImport($url);
+
+ if($fullpath && ($fsize = filesize($fullpath)) !== false) {
+ // IE8 can't handle data uris larger than 32KB
+ if($fsize/1024 < 32) {
+ if(is_null($mime)) {
+ if(class_exists('finfo')) { // php 5.3+
+ $finfo = new finfo(FILEINFO_MIME);
+ $mime = explode('; ', $finfo->file($fullpath));
+ $mime = $mime[0];
+ } elseif(function_exists('mime_content_type')) { // PHP 5.2
+ $mime = mime_content_type($fullpath);
+ }
+ }
+
+ if(!is_null($mime)) // fallback if the mime type is still unknown
+ $url = sprintf('data:%s;base64,%s', $mime, base64_encode(file_get_contents($fullpath)));
+ }
+ }
+
+ return 'url("'.$url.'")';
+ }
+
+ // utility func to unquote a string
+ protected function lib_e($arg) {
+ switch ($arg[0]) {
+ case "list":
+ $items = $arg[2];
+ if (isset($items[0])) {
+ return $this->lib_e($items[0]);
+ }
+ $this->throwError("unrecognised input");
+ case "string":
+ $arg[1] = "";
+ return $arg;
+ case "keyword":
+ return $arg;
+ default:
+ return array("keyword", $this->compileValue($arg));
+ }
+ }
+
+ protected function lib__sprintf($args) {
+ if ($args[0] != "list") return $args;
+ $values = $args[2];
+ $string = array_shift($values);
+ $template = $this->compileValue($this->lib_e($string));
+
+ $i = 0;
+ if (preg_match_all('/%[dsa]/', $template, $m)) {
+ foreach ($m[0] as $match) {
+ $val = isset($values[$i]) ?
+ $this->reduce($values[$i]) : array('keyword', '');
+
+ // lessjs compat, renders fully expanded color, not raw color
+ if ($color = $this->coerceColor($val)) {
+ $val = $color;
+ }
+
+ $i++;
+ $rep = $this->compileValue($this->lib_e($val));
+ $template = preg_replace('/'.self::preg_quote($match).'/',
+ $rep, $template, 1);
+ }
+ }
+
+ $d = $string[0] == "string" ? $string[1] : '"';
+ return array("string", $d, array($template));
+ }
+
+ protected function lib_floor($arg) {
+ $value = $this->assertNumber($arg);
+ return array("number", floor($value), $arg[2]);
+ }
+
+ protected function lib_ceil($arg) {
+ $value = $this->assertNumber($arg);
+ return array("number", ceil($value), $arg[2]);
+ }
+
+ protected function lib_round($arg) {
+ if($arg[0] != "list") {
+ $value = $this->assertNumber($arg);
+ return array("number", round($value), $arg[2]);
+ } else {
+ $value = $this->assertNumber($arg[2][0]);
+ $precision = $this->assertNumber($arg[2][1]);
+ return array("number", round($value, $precision), $arg[2][0][2]);
+ }
+ }
+
+ protected function lib_unit($arg) {
+ if ($arg[0] == "list") {
+ list($number, $newUnit) = $arg[2];
+ return array("number", $this->assertNumber($number),
+ $this->compileValue($this->lib_e($newUnit)));
+ } else {
+ return array("number", $this->assertNumber($arg), "");
+ }
+ }
+
+ /**
+ * Helper function to get arguments for color manipulation functions.
+ * takes a list that contains a color like thing and a percentage
+ */
+ public function colorArgs($args) {
+ if ($args[0] != 'list' || count($args[2]) < 2) {
+ return array(array('color', 0, 0, 0), 0);
+ }
+ list($color, $delta) = $args[2];
+ $color = $this->assertColor($color);
+ $delta = floatval($delta[1]);
+
+ return array($color, $delta);
+ }
+
+ protected function lib_darken($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[3] = $this->clamp($hsl[3] - $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_lighten($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[3] = $this->clamp($hsl[3] + $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_saturate($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[2] = $this->clamp($hsl[2] + $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_desaturate($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+ $hsl[2] = $this->clamp($hsl[2] - $delta, 100);
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_spin($args) {
+ list($color, $delta) = $this->colorArgs($args);
+
+ $hsl = $this->toHSL($color);
+
+ $hsl[1] = $hsl[1] + $delta % 360;
+ if ($hsl[1] < 0) $hsl[1] += 360;
+
+ return $this->toRGB($hsl);
+ }
+
+ protected function lib_fadeout($args) {
+ list($color, $delta) = $this->colorArgs($args);
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) - $delta/100);
+ return $color;
+ }
+
+ protected function lib_fadein($args) {
+ list($color, $delta) = $this->colorArgs($args);
+ $color[4] = $this->clamp((isset($color[4]) ? $color[4] : 1) + $delta/100);
+ return $color;
+ }
+
+ protected function lib_hue($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[1]);
+ }
+
+ protected function lib_saturation($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[2]);
+ }
+
+ protected function lib_lightness($color) {
+ $hsl = $this->toHSL($this->assertColor($color));
+ return round($hsl[3]);
+ }
+
+ // get the alpha of a color
+ // defaults to 1 for non-colors or colors without an alpha
+ protected function lib_alpha($value) {
+ if (!is_null($color = $this->coerceColor($value))) {
+ return isset($color[4]) ? $color[4] : 1;
+ }
+ }
+
+ // set the alpha of the color
+ protected function lib_fade($args) {
+ list($color, $alpha) = $this->colorArgs($args);
+ $color[4] = $this->clamp($alpha / 100.0);
+ return $color;
+ }
+
+ protected function lib_percentage($arg) {
+ $num = $this->assertNumber($arg);
+ return array("number", $num*100, "%");
+ }
+
+ // mixes two colors by weight
+ // mix(@color1, @color2, [@weight: 50%]);
+ // http://sass-lang.com/docs/yardoc/Sass/Script/Functions.html#mix-instance_method
+ protected function lib_mix($args) {
+ if ($args[0] != "list" || count($args[2]) < 2)
+ $this->throwError("mix expects (color1, color2, weight)");
+
+ list($first, $second) = $args[2];
+ $first = $this->assertColor($first);
+ $second = $this->assertColor($second);
+
+ $first_a = $this->lib_alpha($first);
+ $second_a = $this->lib_alpha($second);
+
+ if (isset($args[2][2])) {
+ $weight = $args[2][2][1] / 100.0;
+ } else {
+ $weight = 0.5;
+ }
+
+ $w = $weight * 2 - 1;
+ $a = $first_a - $second_a;
+
+ $w1 = (($w * $a == -1 ? $w : ($w + $a)/(1 + $w * $a)) + 1) / 2.0;
+ $w2 = 1.0 - $w1;
+
+ $new = array('color',
+ $w1 * $first[1] + $w2 * $second[1],
+ $w1 * $first[2] + $w2 * $second[2],
+ $w1 * $first[3] + $w2 * $second[3],
+ );
+
+ if ($first_a != 1.0 || $second_a != 1.0) {
+ $new[] = $first_a * $weight + $second_a * ($weight - 1);
+ }
+
+ return $this->fixColor($new);
+ }
+
+ protected function lib_contrast($args) {
+ $darkColor = array('color', 0, 0, 0);
+ $lightColor = array('color', 255, 255, 255);
+ $threshold = 0.43;
+
+ if ( $args[0] == 'list' ) {
+ $inputColor = ( isset($args[2][0]) ) ? $this->assertColor($args[2][0]) : $lightColor;
+ $darkColor = ( isset($args[2][1]) ) ? $this->assertColor($args[2][1]) : $darkColor;
+ $lightColor = ( isset($args[2][2]) ) ? $this->assertColor($args[2][2]) : $lightColor;
+ $threshold = ( isset($args[2][3]) ) ? $this->assertNumber($args[2][3]) : $threshold;
+ }
+ else {
+ $inputColor = $this->assertColor($args);
+ }
+
+ $inputColor = $this->coerceColor($inputColor);
+ $darkColor = $this->coerceColor($darkColor);
+ $lightColor = $this->coerceColor($lightColor);
+
+ //Figure out which is actually light and dark!
+ if ( $this->lib_luma($darkColor) > $this->lib_luma($lightColor) ) {
+ $t = $lightColor;
+ $lightColor = $darkColor;
+ $darkColor = $t;
+ }
+
+ $inputColor_alpha = $this->lib_alpha($inputColor);
+ if ( ( $this->lib_luma($inputColor) * $inputColor_alpha) < $threshold) {
+ return $lightColor;
+ }
+ return $darkColor;
+ }
+
+ protected function lib_luma($color) {
+ $color = $this->coerceColor($color);
+ return (0.2126 * $color[0] / 255) + (0.7152 * $color[1] / 255) + (0.0722 * $color[2] / 255);
+ }
+
+
+ public function assertColor($value, $error = "expected color value") {
+ $color = $this->coerceColor($value);
+ if (is_null($color)) $this->throwError($error);
+ return $color;
+ }
+
+ public function assertNumber($value, $error = "expecting number") {
+ if ($value[0] == "number") return $value[1];
+ $this->throwError($error);
+ }
+
+ public function assertArgs($value, $expectedArgs, $name="") {
+ if ($expectedArgs == 1) {
+ return $value;
+ } else {
+ if ($value[0] !== "list" || $value[1] != ",") $this->throwError("expecting list");
+ $values = $value[2];
+ $numValues = count($values);
+ if ($expectedArgs != $numValues) {
+ if ($name) {
+ $name = $name . ": ";
+ }
+
+ $this->throwError("${name}expecting $expectedArgs arguments, got $numValues");
+ }
+
+ return $values;
+ }
+ }
+
+ protected function toHSL($color) {
+ if ($color[0] == 'hsl') return $color;
+
+ $r = $color[1] / 255;
+ $g = $color[2] / 255;
+ $b = $color[3] / 255;
+
+ $min = min($r, $g, $b);
+ $max = max($r, $g, $b);
+
+ $L = ($min + $max) / 2;
+ if ($min == $max) {
+ $S = $H = 0;
+ } else {
+ if ($L < 0.5)
+ $S = ($max - $min)/($max + $min);
+ else
+ $S = ($max - $min)/(2.0 - $max - $min);
+
+ if ($r == $max) $H = ($g - $b)/($max - $min);
+ elseif ($g == $max) $H = 2.0 + ($b - $r)/($max - $min);
+ elseif ($b == $max) $H = 4.0 + ($r - $g)/($max - $min);
+
+ }
+
+ $out = array('hsl',
+ ($H < 0 ? $H + 6 : $H)*60,
+ $S*100,
+ $L*100,
+ );
+
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
+ return $out;
+ }
+
+ protected function toRGB_helper($comp, $temp1, $temp2) {
+ if ($comp < 0) $comp += 1.0;
+ elseif ($comp > 1) $comp -= 1.0;
+
+ if (6 * $comp < 1) return $temp1 + ($temp2 - $temp1) * 6 * $comp;
+ if (2 * $comp < 1) return $temp2;
+ if (3 * $comp < 2) return $temp1 + ($temp2 - $temp1)*((2/3) - $comp) * 6;
+
+ return $temp1;
+ }
+
+ /**
+ * Converts a hsl array into a color value in rgb.
+ * Expects H to be in range of 0 to 360, S and L in 0 to 100
+ */
+ protected function toRGB($color) {
+ if ($color[0] == 'color') return $color;
+
+ $H = $color[1] / 360;
+ $S = $color[2] / 100;
+ $L = $color[3] / 100;
+
+ if ($S == 0) {
+ $r = $g = $b = $L;
+ } else {
+ $temp2 = $L < 0.5 ?
+ $L*(1.0 + $S) :
+ $L + $S - $L * $S;
+
+ $temp1 = 2.0 * $L - $temp2;
+
+ $r = $this->toRGB_helper($H + 1/3, $temp1, $temp2);
+ $g = $this->toRGB_helper($H, $temp1, $temp2);
+ $b = $this->toRGB_helper($H - 1/3, $temp1, $temp2);
+ }
+
+ // $out = array('color', round($r*255), round($g*255), round($b*255));
+ $out = array('color', $r*255, $g*255, $b*255);
+ if (count($color) > 4) $out[] = $color[4]; // copy alpha
+ return $out;
+ }
+
+ protected function clamp($v, $max = 1, $min = 0) {
+ return min($max, max($min, $v));
+ }
+
+ /**
+ * Convert the rgb, rgba, hsl color literals of function type
+ * as returned by the parser into values of color type.
+ */
+ protected function funcToColor($func) {
+ $fname = $func[1];
+ if ($func[2][0] != 'list') return false; // need a list of arguments
+ $rawComponents = $func[2][2];
+
+ if ($fname == 'hsl' || $fname == 'hsla') {
+ $hsl = array('hsl');
+ $i = 0;
+ foreach ($rawComponents as $c) {
+ $val = $this->reduce($c);
+ $val = isset($val[1]) ? floatval($val[1]) : 0;
+
+ if ($i == 0) $clamp = 360;
+ elseif ($i < 3) $clamp = 100;
+ else $clamp = 1;
+
+ $hsl[] = $this->clamp($val, $clamp);
+ $i++;
+ }
+
+ while (count($hsl) < 4) $hsl[] = 0;
+ return $this->toRGB($hsl);
+
+ } elseif ($fname == 'rgb' || $fname == 'rgba') {
+ $components = array();
+ $i = 1;
+ foreach ($rawComponents as $c) {
+ $c = $this->reduce($c);
+ if ($i < 4) {
+ if ($c[0] == "number" && $c[2] == "%") {
+ $components[] = 255 * ($c[1] / 100);
+ } else {
+ $components[] = floatval($c[1]);
+ }
+ } elseif ($i == 4) {
+ if ($c[0] == "number" && $c[2] == "%") {
+ $components[] = 1.0 * ($c[1] / 100);
+ } else {
+ $components[] = floatval($c[1]);
+ }
+ } else break;
+
+ $i++;
+ }
+ while (count($components) < 3) $components[] = 0;
+ array_unshift($components, 'color');
+ return $this->fixColor($components);
+ }
+
+ return false;
+ }
+
+ protected function reduce($value, $forExpression = false) {
+ switch ($value[0]) {
+ case "interpolate":
+ $reduced = $this->reduce($value[1]);
+ $var = $this->compileValue($reduced);
+ $res = $this->reduce(array("variable", $this->vPrefix . $var));
+
+ if ($res[0] == "raw_color") {
+ $res = $this->coerceColor($res);
+ }
+
+ if (empty($value[2])) $res = $this->lib_e($res);
+
+ return $res;
+ case "variable":
+ $key = $value[1];
+ if (is_array($key)) {
+ $key = $this->reduce($key);
+ $key = $this->vPrefix . $this->compileValue($this->lib_e($key));
+ }
+
+ $seen =& $this->env->seenNames;
+
+ if (!empty($seen[$key])) {
+ $this->throwError("infinite loop detected: $key");
+ }
+
+ $seen[$key] = true;
+ $out = $this->reduce($this->get($key));
+ $seen[$key] = false;
+ return $out;
+ case "list":
+ foreach ($value[2] as &$item) {
+ $item = $this->reduce($item, $forExpression);
+ }
+ return $value;
+ case "expression":
+ return $this->evaluate($value);
+ case "string":
+ foreach ($value[2] as &$part) {
+ if (is_array($part)) {
+ $strip = $part[0] == "variable";
+ $part = $this->reduce($part);
+ if ($strip) $part = $this->lib_e($part);
+ }
+ }
+ return $value;
+ case "escape":
+ list(,$inner) = $value;
+ return $this->lib_e($this->reduce($inner));
+ case "function":
+ $color = $this->funcToColor($value);
+ if ($color) return $color;
+
+ list(, $name, $args) = $value;
+ if ($name == "%") $name = "_sprintf";
+
+ $f = isset($this->libFunctions[$name]) ?
+ $this->libFunctions[$name] : array($this, 'lib_'.str_replace('-', '_', $name));
+
+ if (is_callable($f)) {
+ if ($args[0] == 'list')
+ $args = self::compressList($args[2], $args[1]);
+
+ $ret = call_user_func($f, $this->reduce($args, true), $this);
+
+ if (is_null($ret)) {
+ return array("string", "", array(
+ $name, "(", $args, ")"
+ ));
+ }
+
+ // convert to a typed value if the result is a php primitive
+ if (is_numeric($ret)) $ret = array('number', $ret, "");
+ elseif (!is_array($ret)) $ret = array('keyword', $ret);
+
+ return $ret;
+ }
+
+ // plain function, reduce args
+ $value[2] = $this->reduce($value[2]);
+ return $value;
+ case "unary":
+ list(, $op, $exp) = $value;
+ $exp = $this->reduce($exp);
+
+ if ($exp[0] == "number") {
+ switch ($op) {
+ case "+":
+ return $exp;
+ case "-":
+ $exp[1] *= -1;
+ return $exp;
+ }
+ }
+ return array("string", "", array($op, $exp));
+ }
+
+ if ($forExpression) {
+ switch ($value[0]) {
+ case "keyword":
+ if ($color = $this->coerceColor($value)) {
+ return $color;
+ }
+ break;
+ case "raw_color":
+ return $this->coerceColor($value);
+ }
+ }
+
+ return $value;
+ }
+
+
+ // coerce a value for use in color operation
+ protected function coerceColor($value) {
+ switch($value[0]) {
+ case 'color': return $value;
+ case 'raw_color':
+ $c = array("color", 0, 0, 0);
+ $colorStr = substr($value[1], 1);
+ $num = hexdec($colorStr);
+ $width = strlen($colorStr) == 3 ? 16 : 256;
+
+ for ($i = 3; $i > 0; $i--) { // 3 2 1
+ $t = $num % $width;
+ $num /= $width;
+
+ $c[$i] = $t * (256/$width) + $t * floor(16/$width);
+ }
+
+ return $c;
+ case 'keyword':
+ $name = $value[1];
+ if (isset(self::$cssColors[$name])) {
+ $rgba = explode(',', self::$cssColors[$name]);
+
+ if(isset($rgba[3]))
+ return array('color', $rgba[0], $rgba[1], $rgba[2], $rgba[3]);
+
+ return array('color', $rgba[0], $rgba[1], $rgba[2]);
+ }
+ return null;
+ }
+ }
+
+ // make something string like into a string
+ protected function coerceString($value) {
+ switch ($value[0]) {
+ case "string":
+ return $value;
+ case "keyword":
+ return array("string", "", array($value[1]));
+ }
+ return null;
+ }
+
+ // turn list of length 1 into value type
+ protected function flattenList($value) {
+ if ($value[0] == "list" && count($value[2]) == 1) {
+ return $this->flattenList($value[2][0]);
+ }
+ return $value;
+ }
+
+ public function toBool($a) {
+ if ($a) return self::$TRUE;
+ else return self::$FALSE;
+ }
+
+ // evaluate an expression
+ protected function evaluate($exp) {
+ list(, $op, $left, $right, $whiteBefore, $whiteAfter) = $exp;
+
+ $left = $this->reduce($left, true);
+ $right = $this->reduce($right, true);
+
+ if ($leftColor = $this->coerceColor($left)) {
+ $left = $leftColor;
+ }
+
+ if ($rightColor = $this->coerceColor($right)) {
+ $right = $rightColor;
+ }
+
+ $ltype = $left[0];
+ $rtype = $right[0];
+
+ // operators that work on all types
+ if ($op == "and") {
+ return $this->toBool($left == self::$TRUE && $right == self::$TRUE);
+ }
+
+ if ($op == "=") {
+ return $this->toBool($this->eq($left, $right) );
+ }
+
+ if ($op == "+" && !is_null($str = $this->stringConcatenate($left, $right))) {
+ return $str;
+ }
+
+ // type based operators
+ $fname = "op_${ltype}_${rtype}";
+ if (is_callable(array($this, $fname))) {
+ $out = $this->$fname($op, $left, $right);
+ if (!is_null($out)) return $out;
+ }
+
+ // make the expression look it did before being parsed
+ $paddedOp = $op;
+ if ($whiteBefore) $paddedOp = " " . $paddedOp;
+ if ($whiteAfter) $paddedOp .= " ";
+
+ return array("string", "", array($left, $paddedOp, $right));
+ }
+
+ protected function stringConcatenate($left, $right) {
+ if ($strLeft = $this->coerceString($left)) {
+ if ($right[0] == "string") {
+ $right[1] = "";
+ }
+ $strLeft[2][] = $right;
+ return $strLeft;
+ }
+
+ if ($strRight = $this->coerceString($right)) {
+ array_unshift($strRight[2], $left);
+ return $strRight;
+ }
+ }
+
+
+ // make sure a color's components don't go out of bounds
+ protected function fixColor($c) {
+ foreach (range(1, 3) as $i) {
+ if ($c[$i] < 0) $c[$i] = 0;
+ if ($c[$i] > 255) $c[$i] = 255;
+ }
+
+ return $c;
+ }
+
+ protected function op_number_color($op, $lft, $rgt) {
+ if ($op == '+' || $op == '*') {
+ return $this->op_color_number($op, $rgt, $lft);
+ }
+ }
+
+ protected function op_color_number($op, $lft, $rgt) {
+ if ($rgt[0] == '%') $rgt[1] /= 100;
+
+ return $this->op_color_color($op, $lft,
+ array_fill(1, count($lft) - 1, $rgt[1]));
+ }
+
+ protected function op_color_color($op, $left, $right) {
+ $out = array('color');
+ $max = count($left) > count($right) ? count($left) : count($right);
+ foreach (range(1, $max - 1) as $i) {
+ $lval = isset($left[$i]) ? $left[$i] : 0;
+ $rval = isset($right[$i]) ? $right[$i] : 0;
+ switch ($op) {
+ case '+':
+ $out[] = $lval + $rval;
+ break;
+ case '-':
+ $out[] = $lval - $rval;
+ break;
+ case '*':
+ $out[] = $lval * $rval;
+ break;
+ case '%':
+ $out[] = $lval % $rval;
+ break;
+ case '/':
+ if ($rval == 0) $this->throwError("evaluate error: can't divide by zero");
+ $out[] = $lval / $rval;
+ break;
+ default:
+ $this->throwError('evaluate error: color op number failed on op '.$op);
+ }
+ }
+ return $this->fixColor($out);
+ }
+
+ function lib_red($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for red()');
+ }
+
+ return $color[1];
+ }
+
+ function lib_green($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for green()');
+ }
+
+ return $color[2];
+ }
+
+ function lib_blue($color){
+ $color = $this->coerceColor($color);
+ if (is_null($color)) {
+ $this->throwError('color expected for blue()');
+ }
+
+ return $color[3];
+ }
+
+
+ // operator on two numbers
+ protected function op_number_number($op, $left, $right) {
+ $unit = empty($left[2]) ? $right[2] : $left[2];
+
+ $value = 0;
+ switch ($op) {
+ case '+':
+ $value = $left[1] + $right[1];
+ break;
+ case '*':
+ $value = $left[1] * $right[1];
+ break;
+ case '-':
+ $value = $left[1] - $right[1];
+ break;
+ case '%':
+ $value = $left[1] % $right[1];
+ break;
+ case '/':
+ if ($right[1] == 0) $this->throwError('parse error: divide by zero');
+ $value = $left[1] / $right[1];
+ break;
+ case '<':
+ return $this->toBool($left[1] < $right[1]);
+ case '>':
+ return $this->toBool($left[1] > $right[1]);
+ case '>=':
+ return $this->toBool($left[1] >= $right[1]);
+ case '=<':
+ return $this->toBool($left[1] <= $right[1]);
+ default:
+ $this->throwError('parse error: unknown number operator: '.$op);
+ }
+
+ return array("number", $value, $unit);
+ }
+
+
+ /* environment functions */
+
+ protected function makeOutputBlock($type, $selectors = null) {
+ $b = new stdclass;
+ $b->lines = array();
+ $b->children = array();
+ $b->selectors = $selectors;
+ $b->type = $type;
+ $b->parent = $this->scope;
+ return $b;
+ }
+
+ // the state of execution
+ protected function pushEnv($block = null) {
+ $e = new stdclass;
+ $e->parent = $this->env;
+ $e->store = array();
+ $e->block = $block;
+
+ $this->env = $e;
+ return $e;
+ }
+
+ // pop something off the stack
+ protected function popEnv() {
+ $old = $this->env;
+ $this->env = $this->env->parent;
+ return $old;
+ }
+
+ // set something in the current env
+ protected function set($name, $value) {
+ $this->env->store[$name] = $value;
+ }
+
+
+ // get the highest occurrence entry for a name
+ protected function get($name) {
+ $current = $this->env;
+
+ $isArguments = $name == $this->vPrefix . 'arguments';
+ while ($current) {
+ if ($isArguments && isset($current->arguments)) {
+ return array('list', ' ', $current->arguments);
+ }
+
+ if (isset($current->store[$name]))
+ return $current->store[$name];
+ else {
+ $current = isset($current->storeParent) ?
+ $current->storeParent : $current->parent;
+ }
+ }
+
+ $this->throwError("variable $name is undefined");
+ }
+
+ // inject array of unparsed strings into environment as variables
+ protected function injectVariables($args) {
+ $this->pushEnv();
+ $parser = new lessc_parser($this, __METHOD__);
+ foreach ($args as $name => $strValue) {
+ if ($name{0} != '@') $name = '@'.$name;
+ $parser->count = 0;
+ $parser->buffer = (string)$strValue;
+ if (!$parser->propertyValue($value)) {
+ throw new Exception("failed to parse passed in variable $name: $strValue");
+ }
+
+ $this->set($name, $value);
+ }
+ }
+
+ /**
+ * Initialize any static state, can initialize parser for a file
+ * $opts isn't used yet
+ */
+ public function __construct($fname = null) {
+ if ($fname !== null) {
+ // used for deprecated parse method
+ $this->_parseFile = $fname;
+ }
+ }
+
+ public function compile($string, $name = null) {
+ $locale = setlocale(LC_NUMERIC, 0);
+ setlocale(LC_NUMERIC, "C");
+
+ $this->parser = $this->makeParser($name);
+ $root = $this->parser->parse($string);
+
+ $this->env = null;
+ $this->scope = null;
+
+ $this->formatter = $this->newFormatter();
+
+ if (!empty($this->registeredVars)) {
+ $this->injectVariables($this->registeredVars);
+ }
+
+ $this->sourceParser = $this->parser; // used for error messages
+ $this->compileBlock($root);
+
+ ob_start();
+ $this->formatter->block($this->scope);
+ $out = ob_get_clean();
+ setlocale(LC_NUMERIC, $locale);
+ return $out;
+ }
+
+ public function compileFile($fname, $outFname = null) {
+ if (!is_readable($fname)) {
+ throw new Exception('load error: failed to find '.$fname);
+ }
+
+ $pi = pathinfo($fname);
+
+ $oldImport = $this->importDir;
+
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = $pi['dirname'].'/';
+
+ $this->addParsedFile($fname);
+
+ $out = $this->compile(file_get_contents($fname), $fname);
+
+ $this->importDir = $oldImport;
+
+ if ($outFname !== null) {
+ return file_put_contents($outFname, $out);
+ }
+
+ return $out;
+ }
+
+ // compile only if changed input has changed or output doesn't exist
+ public function checkedCompile($in, $out) {
+ if (!is_file($out) || filemtime($in) > filemtime($out)) {
+ $this->compileFile($in, $out);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Execute lessphp on a .less file or a lessphp cache structure
+ *
+ * The lessphp cache structure contains information about a specific
+ * less file having been parsed. It can be used as a hint for future
+ * calls to determine whether or not a rebuild is required.
+ *
+ * The cache structure contains two important keys that may be used
+ * externally:
+ *
+ * compiled: The final compiled CSS
+ * updated: The time (in seconds) the CSS was last compiled
+ *
+ * The cache structure is a plain-ol' PHP associative array and can
+ * be serialized and unserialized without a hitch.
+ *
+ * @param mixed $in Input
+ * @param bool $force Force rebuild?
+ * @return array lessphp cache structure
+ */
+ public function cachedCompile($in, $force = false) {
+ // assume no root
+ $root = null;
+
+ if (is_string($in)) {
+ $root = $in;
+ } elseif (is_array($in) and isset($in['root'])) {
+ if ($force or ! isset($in['files'])) {
+ // If we are forcing a recompile or if for some reason the
+ // structure does not contain any file information we should
+ // specify the root to trigger a rebuild.
+ $root = $in['root'];
+ } elseif (isset($in['files']) and is_array($in['files'])) {
+ foreach ($in['files'] as $fname => $ftime ) {
+ if (!file_exists($fname) or filemtime($fname) > $ftime) {
+ // One of the files we knew about previously has changed
+ // so we should look at our incoming root again.
+ $root = $in['root'];
+ break;
+ }
+ }
+ }
+ } else {
+ // TODO: Throw an exception? We got neither a string nor something
+ // that looks like a compatible lessphp cache structure.
+ return null;
+ }
+
+ if ($root !== null) {
+ // If we have a root value which means we should rebuild.
+ $out = array();
+ $out['root'] = $root;
+ $out['compiled'] = $this->compileFile($root);
+ $out['files'] = $this->allParsedFiles();
+ $out['updated'] = time();
+ return $out;
+ } else {
+ // No changes, pass back the structure
+ // we were given initially.
+ return $in;
+ }
+
+ }
+
+ // parse and compile buffer
+ // This is deprecated
+ public function parse($str = null, $initialVariables = null) {
+ if (is_array($str)) {
+ $initialVariables = $str;
+ $str = null;
+ }
+
+ $oldVars = $this->registeredVars;
+ if ($initialVariables !== null) {
+ $this->setVariables($initialVariables);
+ }
+
+ if ($str == null) {
+ if (empty($this->_parseFile)) {
+ throw new exception("nothing to parse");
+ }
+
+ $out = $this->compileFile($this->_parseFile);
+ } else {
+ $out = $this->compile($str);
+ }
+
+ $this->registeredVars = $oldVars;
+ return $out;
+ }
+
+ protected function makeParser($name) {
+ $parser = new lessc_parser($this, $name);
+ $parser->writeComments = $this->preserveComments;
+
+ return $parser;
+ }
+
+ public function setFormatter($name) {
+ $this->formatterName = $name;
+ }
+
+ protected function newFormatter() {
+ $className = "lessc_formatter_lessjs";
+ if (!empty($this->formatterName)) {
+ if (!is_string($this->formatterName))
+ return $this->formatterName;
+ $className = "lessc_formatter_$this->formatterName";
+ }
+
+ return new $className;
+ }
+
+ public function setPreserveComments($preserve) {
+ $this->preserveComments = $preserve;
+ }
+
+ public function registerFunction($name, $func) {
+ $this->libFunctions[$name] = $func;
+ }
+
+ public function unregisterFunction($name) {
+ unset($this->libFunctions[$name]);
+ }
+
+ public function setVariables($variables) {
+ $this->registeredVars = array_merge($this->registeredVars, $variables);
+ }
+
+ public function unsetVariable($name) {
+ unset($this->registeredVars[$name]);
+ }
+
+ public function setImportDir($dirs) {
+ $this->importDir = (array)$dirs;
+ }
+
+ public function addImportDir($dir) {
+ $this->importDir = (array)$this->importDir;
+ $this->importDir[] = $dir;
+ }
+
+ public function allParsedFiles() {
+ return $this->allParsedFiles;
+ }
+
+ public function addParsedFile($file) {
+ $this->allParsedFiles[realpath($file)] = filemtime($file);
+ }
+
+ /**
+ * Uses the current value of $this->count to show line and line number
+ */
+ public function throwError($msg = null) {
+ if ($this->sourceLoc >= 0) {
+ $this->sourceParser->throwError($msg, $this->sourceLoc);
+ }
+ throw new exception($msg);
+ }
+
+ // compile file $in to file $out if $in is newer than $out
+ // returns true when it compiles, false otherwise
+ public static function ccompile($in, $out, $less = null) {
+ if ($less === null) {
+ $less = new self;
+ }
+ return $less->checkedCompile($in, $out);
+ }
+
+ public static function cexecute($in, $force = false, $less = null) {
+ if ($less === null) {
+ $less = new self;
+ }
+ return $less->cachedCompile($in, $force);
+ }
+
+ static protected $cssColors = array(
+ 'aliceblue' => '240,248,255',
+ 'antiquewhite' => '250,235,215',
+ 'aqua' => '0,255,255',
+ 'aquamarine' => '127,255,212',
+ 'azure' => '240,255,255',
+ 'beige' => '245,245,220',
+ 'bisque' => '255,228,196',
+ 'black' => '0,0,0',
+ 'blanchedalmond' => '255,235,205',
+ 'blue' => '0,0,255',
+ 'blueviolet' => '138,43,226',
+ 'brown' => '165,42,42',
+ 'burlywood' => '222,184,135',
+ 'cadetblue' => '95,158,160',
+ 'chartreuse' => '127,255,0',
+ 'chocolate' => '210,105,30',
+ 'coral' => '255,127,80',
+ 'cornflowerblue' => '100,149,237',
+ 'cornsilk' => '255,248,220',
+ 'crimson' => '220,20,60',
+ 'cyan' => '0,255,255',
+ 'darkblue' => '0,0,139',
+ 'darkcyan' => '0,139,139',
+ 'darkgoldenrod' => '184,134,11',
+ 'darkgray' => '169,169,169',
+ 'darkgreen' => '0,100,0',
+ 'darkgrey' => '169,169,169',
+ 'darkkhaki' => '189,183,107',
+ 'darkmagenta' => '139,0,139',
+ 'darkolivegreen' => '85,107,47',
+ 'darkorange' => '255,140,0',
+ 'darkorchid' => '153,50,204',
+ 'darkred' => '139,0,0',
+ 'darksalmon' => '233,150,122',
+ 'darkseagreen' => '143,188,143',
+ 'darkslateblue' => '72,61,139',
+ 'darkslategray' => '47,79,79',
+ 'darkslategrey' => '47,79,79',
+ 'darkturquoise' => '0,206,209',
+ 'darkviolet' => '148,0,211',
+ 'deeppink' => '255,20,147',
+ 'deepskyblue' => '0,191,255',
+ 'dimgray' => '105,105,105',
+ 'dimgrey' => '105,105,105',
+ 'dodgerblue' => '30,144,255',
+ 'firebrick' => '178,34,34',
+ 'floralwhite' => '255,250,240',
+ 'forestgreen' => '34,139,34',
+ 'fuchsia' => '255,0,255',
+ 'gainsboro' => '220,220,220',
+ 'ghostwhite' => '248,248,255',
+ 'gold' => '255,215,0',
+ 'goldenrod' => '218,165,32',
+ 'gray' => '128,128,128',
+ 'green' => '0,128,0',
+ 'greenyellow' => '173,255,47',
+ 'grey' => '128,128,128',
+ 'honeydew' => '240,255,240',
+ 'hotpink' => '255,105,180',
+ 'indianred' => '205,92,92',
+ 'indigo' => '75,0,130',
+ 'ivory' => '255,255,240',
+ 'khaki' => '240,230,140',
+ 'lavender' => '230,230,250',
+ 'lavenderblush' => '255,240,245',
+ 'lawngreen' => '124,252,0',
+ 'lemonchiffon' => '255,250,205',
+ 'lightblue' => '173,216,230',
+ 'lightcoral' => '240,128,128',
+ 'lightcyan' => '224,255,255',
+ 'lightgoldenrodyellow' => '250,250,210',
+ 'lightgray' => '211,211,211',
+ 'lightgreen' => '144,238,144',
+ 'lightgrey' => '211,211,211',
+ 'lightpink' => '255,182,193',
+ 'lightsalmon' => '255,160,122',
+ 'lightseagreen' => '32,178,170',
+ 'lightskyblue' => '135,206,250',
+ 'lightslategray' => '119,136,153',
+ 'lightslategrey' => '119,136,153',
+ 'lightsteelblue' => '176,196,222',
+ 'lightyellow' => '255,255,224',
+ 'lime' => '0,255,0',
+ 'limegreen' => '50,205,50',
+ 'linen' => '250,240,230',
+ 'magenta' => '255,0,255',
+ 'maroon' => '128,0,0',
+ 'mediumaquamarine' => '102,205,170',
+ 'mediumblue' => '0,0,205',
+ 'mediumorchid' => '186,85,211',
+ 'mediumpurple' => '147,112,219',
+ 'mediumseagreen' => '60,179,113',
+ 'mediumslateblue' => '123,104,238',
+ 'mediumspringgreen' => '0,250,154',
+ 'mediumturquoise' => '72,209,204',
+ 'mediumvioletred' => '199,21,133',
+ 'midnightblue' => '25,25,112',
+ 'mintcream' => '245,255,250',
+ 'mistyrose' => '255,228,225',
+ 'moccasin' => '255,228,181',
+ 'navajowhite' => '255,222,173',
+ 'navy' => '0,0,128',
+ 'oldlace' => '253,245,230',
+ 'olive' => '128,128,0',
+ 'olivedrab' => '107,142,35',
+ 'orange' => '255,165,0',
+ 'orangered' => '255,69,0',
+ 'orchid' => '218,112,214',
+ 'palegoldenrod' => '238,232,170',
+ 'palegreen' => '152,251,152',
+ 'paleturquoise' => '175,238,238',
+ 'palevioletred' => '219,112,147',
+ 'papayawhip' => '255,239,213',
+ 'peachpuff' => '255,218,185',
+ 'peru' => '205,133,63',
+ 'pink' => '255,192,203',
+ 'plum' => '221,160,221',
+ 'powderblue' => '176,224,230',
+ 'purple' => '128,0,128',
+ 'red' => '255,0,0',
+ 'rosybrown' => '188,143,143',
+ 'royalblue' => '65,105,225',
+ 'saddlebrown' => '139,69,19',
+ 'salmon' => '250,128,114',
+ 'sandybrown' => '244,164,96',
+ 'seagreen' => '46,139,87',
+ 'seashell' => '255,245,238',
+ 'sienna' => '160,82,45',
+ 'silver' => '192,192,192',
+ 'skyblue' => '135,206,235',
+ 'slateblue' => '106,90,205',
+ 'slategray' => '112,128,144',
+ 'slategrey' => '112,128,144',
+ 'snow' => '255,250,250',
+ 'springgreen' => '0,255,127',
+ 'steelblue' => '70,130,180',
+ 'tan' => '210,180,140',
+ 'teal' => '0,128,128',
+ 'thistle' => '216,191,216',
+ 'tomato' => '255,99,71',
+ 'transparent' => '0,0,0,0',
+ 'turquoise' => '64,224,208',
+ 'violet' => '238,130,238',
+ 'wheat' => '245,222,179',
+ 'white' => '255,255,255',
+ 'whitesmoke' => '245,245,245',
+ 'yellow' => '255,255,0',
+ 'yellowgreen' => '154,205,50'
+ );
+}
+
+// responsible for taking a string of LESS code and converting it into a
+// syntax tree
+class lessc_parser {
+ static protected $nextBlockId = 0; // used to uniquely identify blocks
+
+ static protected $precedence = array(
+ '=<' => 0,
+ '>=' => 0,
+ '=' => 0,
+ '<' => 0,
+ '>' => 0,
+
+ '+' => 1,
+ '-' => 1,
+ '*' => 2,
+ '/' => 2,
+ '%' => 2,
+ );
+
+ static protected $whitePattern;
+ static protected $commentMulti;
+
+ static protected $commentSingle = "//";
+ static protected $commentMultiLeft = "/*";
+ static protected $commentMultiRight = "*/";
+
+ // regex string to match any of the operators
+ static protected $operatorString;
+
+ // these properties will supress division unless it's inside parenthases
+ static protected $supressDivisionProps =
+ array('/border-radius$/i', '/^font$/i');
+
+ protected $blockDirectives = array("font-face", "keyframes", "page", "-moz-document", "viewport", "-moz-viewport", "-o-viewport", "-ms-viewport");
+ protected $lineDirectives = array("charset");
+
+ /**
+ * if we are in parens we can be more liberal with whitespace around
+ * operators because it must evaluate to a single value and thus is less
+ * ambiguous.
+ *
+ * Consider:
+ * property1: 10 -5; // is two numbers, 10 and -5
+ * property2: (10 -5); // should evaluate to 5
+ */
+ protected $inParens = false;
+
+ // caches preg escaped literals
+ static protected $literalCache = array();
+
+ public function __construct($lessc, $sourceName = null) {
+ $this->eatWhiteDefault = true;
+ // reference to less needed for vPrefix, mPrefix, and parentSelector
+ $this->lessc = $lessc;
+
+ $this->sourceName = $sourceName; // name used for error messages
+
+ $this->writeComments = false;
+
+ if (!self::$operatorString) {
+ self::$operatorString =
+ '('.implode('|', array_map(array('lessc', 'preg_quote'),
+ array_keys(self::$precedence))).')';
+
+ $commentSingle = lessc::preg_quote(self::$commentSingle);
+ $commentMultiLeft = lessc::preg_quote(self::$commentMultiLeft);
+ $commentMultiRight = lessc::preg_quote(self::$commentMultiRight);
+
+ self::$commentMulti = $commentMultiLeft.'.*?'.$commentMultiRight;
+ self::$whitePattern = '/'.$commentSingle.'[^\n]*\s*|('.self::$commentMulti.')\s*|\s+/Ais';
+ }
+ }
+
+ public function parse($buffer) {
+ $this->count = 0;
+ $this->line = 1;
+
+ $this->env = null; // block stack
+ $this->buffer = $this->writeComments ? $buffer : $this->removeComments($buffer);
+ $this->pushSpecialBlock("root");
+ $this->eatWhiteDefault = true;
+ $this->seenComments = array();
+
+ // trim whitespace on head
+ // if (preg_match('/^\s+/', $this->buffer, $m)) {
+ // $this->line += substr_count($m[0], "\n");
+ // $this->buffer = ltrim($this->buffer);
+ // }
+ $this->whitespace();
+
+ // parse the entire file
+ while (false !== $this->parseChunk());
+
+ if ($this->count != strlen($this->buffer))
+ $this->throwError();
+
+ // TODO report where the block was opened
+ if ( !property_exists($this->env, 'parent') || !is_null($this->env->parent) )
+ throw new exception('parse error: unclosed block');
+
+ return $this->env;
+ }
+
+ /**
+ * Parse a single chunk off the head of the buffer and append it to the
+ * current parse environment.
+ * Returns false when the buffer is empty, or when there is an error.
+ *
+ * This function is called repeatedly until the entire document is
+ * parsed.
+ *
+ * This parser is most similar to a recursive descent parser. Single
+ * functions represent discrete grammatical rules for the language, and
+ * they are able to capture the text that represents those rules.
+ *
+ * Consider the function lessc::keyword(). (all parse functions are
+ * structured the same)
+ *
+ * The function takes a single reference argument. When calling the
+ * function it will attempt to match a keyword on the head of the buffer.
+ * If it is successful, it will place the keyword in the referenced
+ * argument, advance the position in the buffer, and return true. If it
+ * fails then it won't advance the buffer and it will return false.
+ *
+ * All of these parse functions are powered by lessc::match(), which behaves
+ * the same way, but takes a literal regular expression. Sometimes it is
+ * more convenient to use match instead of creating a new function.
+ *
+ * Because of the format of the functions, to parse an entire string of
+ * grammatical rules, you can chain them together using &&.
+ *
+ * But, if some of the rules in the chain succeed before one fails, then
+ * the buffer position will be left at an invalid state. In order to
+ * avoid this, lessc::seek() is used to remember and set buffer positions.
+ *
+ * Before parsing a chain, use $s = $this->seek() to remember the current
+ * position into $s. Then if a chain fails, use $this->seek($s) to
+ * go back where we started.
+ */
+ protected function parseChunk() {
+ if (empty($this->buffer)) return false;
+ $s = $this->seek();
+
+ if ($this->whitespace()) {
+ return true;
+ }
+
+ // setting a property
+ if ($this->keyword($key) && $this->assign() &&
+ $this->propertyValue($value, $key) && $this->end())
+ {
+ $this->append(array('assign', $key, $value), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+
+ // look for special css blocks
+ if ($this->literal('@', false)) {
+ $this->count--;
+
+ // media
+ if ($this->literal('@media')) {
+ if (($this->mediaQueryList($mediaQueries) || true)
+ && $this->literal('{'))
+ {
+ $media = $this->pushSpecialBlock("media");
+ $media->queries = is_null($mediaQueries) ? array() : $mediaQueries;
+ return true;
+ } else {
+ $this->seek($s);
+ return false;
+ }
+ }
+
+ if ($this->literal("@", false) && $this->keyword($dirName)) {
+ if ($this->isDirective($dirName, $this->blockDirectives)) {
+ if (($this->openString("{", $dirValue, null, array(";")) || true) &&
+ $this->literal("{"))
+ {
+ $dir = $this->pushSpecialBlock("directive");
+ $dir->name = $dirName;
+ if (isset($dirValue)) $dir->value = $dirValue;
+ return true;
+ }
+ } elseif ($this->isDirective($dirName, $this->lineDirectives)) {
+ if ($this->propertyValue($dirValue) && $this->end()) {
+ $this->append(array("directive", $dirName, $dirValue));
+ return true;
+ }
+ }
+ }
+
+ $this->seek($s);
+ }
+
+ // setting a variable
+ if ($this->variable($var) && $this->assign() &&
+ $this->propertyValue($value) && $this->end())
+ {
+ $this->append(array('assign', $var, $value), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ if ($this->import($importValue)) {
+ $this->append($importValue, $s);
+ return true;
+ }
+
+ // opening parametric mixin
+ if ($this->tag($tag, true) && $this->argumentDef($args, $isVararg) &&
+ ($this->guards($guards) || true) &&
+ $this->literal('{'))
+ {
+ $block = $this->pushBlock($this->fixTags(array($tag)));
+ $block->args = $args;
+ $block->isVararg = $isVararg;
+ if (!empty($guards)) $block->guards = $guards;
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // opening a simple block
+ if ($this->tags($tags) && $this->literal('{', false)) {
+ $tags = $this->fixTags($tags);
+ $this->pushBlock($tags);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // closing a block
+ if ($this->literal('}', false)) {
+ try {
+ $block = $this->pop();
+ } catch (exception $e) {
+ $this->seek($s);
+ $this->throwError($e->getMessage());
+ }
+
+ $hidden = false;
+ if (is_null($block->type)) {
+ $hidden = true;
+ if (!isset($block->args)) {
+ foreach ($block->tags as $tag) {
+ if (!is_string($tag) || $tag{0} != $this->lessc->mPrefix) {
+ $hidden = false;
+ break;
+ }
+ }
+ }
+
+ foreach ($block->tags as $tag) {
+ if (is_string($tag)) {
+ $this->env->children[$tag][] = $block;
+ }
+ }
+ }
+
+ if (!$hidden) {
+ $this->append(array('block', $block), $s);
+ }
+
+ // this is done here so comments aren't bundled into he block that
+ // was just closed
+ $this->whitespace();
+ return true;
+ }
+
+ // mixin
+ if ($this->mixinTags($tags) &&
+ ($this->argumentDef($argv, $isVararg) || true) &&
+ ($this->keyword($suffix) || true) && $this->end())
+ {
+ $tags = $this->fixTags($tags);
+ $this->append(array('mixin', $tags, $argv, $suffix), $s);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // spare ;
+ if ($this->literal(';')) return true;
+
+ return false; // got nothing, throw error
+ }
+
+ protected function isDirective($dirname, $directives) {
+ // TODO: cache pattern in parser
+ $pattern = implode("|",
+ array_map(array("lessc", "preg_quote"), $directives));
+ $pattern = '/^(-[a-z-]+-)?(' . $pattern . ')$/i';
+
+ return preg_match($pattern, $dirname);
+ }
+
+ protected function fixTags($tags) {
+ // move @ tags out of variable namespace
+ foreach ($tags as &$tag) {
+ if ($tag{0} == $this->lessc->vPrefix)
+ $tag[0] = $this->lessc->mPrefix;
+ }
+ return $tags;
+ }
+
+ // a list of expressions
+ protected function expressionList(&$exps) {
+ $values = array();
+
+ while ($this->expression($exp)) {
+ $values[] = $exp;
+ }
+
+ if (count($values) == 0) return false;
+
+ $exps = lessc::compressList($values, ' ');
+ return true;
+ }
+
+ /**
+ * Attempt to consume an expression.
+ * @link http://en.wikipedia.org/wiki/Operator-precedence_parser#Pseudo-code
+ */
+ protected function expression(&$out) {
+ if ($this->value($lhs)) {
+ $out = $this->expHelper($lhs, 0);
+
+ // look for / shorthand
+ if (!empty($this->env->supressedDivision)) {
+ unset($this->env->supressedDivision);
+ $s = $this->seek();
+ if ($this->literal("/") && $this->value($rhs)) {
+ $out = array("list", "",
+ array($out, array("keyword", "/"), $rhs));
+ } else {
+ $this->seek($s);
+ }
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * recursively parse infix equation with $lhs at precedence $minP
+ */
+ protected function expHelper($lhs, $minP) {
+ $this->inExp = true;
+ $ss = $this->seek();
+
+ while (true) {
+ $whiteBefore = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+
+ // If there is whitespace before the operator, then we require
+ // whitespace after the operator for it to be an expression
+ $needWhite = $whiteBefore && !$this->inParens;
+
+ if ($this->match(self::$operatorString.($needWhite ? '\s' : ''), $m) && self::$precedence[$m[1]] >= $minP) {
+ if (!$this->inParens && isset($this->env->currentProperty) && $m[1] == "/" && empty($this->env->supressedDivision)) {
+ foreach (self::$supressDivisionProps as $pattern) {
+ if (preg_match($pattern, $this->env->currentProperty)) {
+ $this->env->supressedDivision = true;
+ break 2;
+ }
+ }
+ }
+
+
+ $whiteAfter = isset($this->buffer[$this->count - 1]) &&
+ ctype_space($this->buffer[$this->count - 1]);
+
+ if (!$this->value($rhs)) break;
+
+ // peek for next operator to see what to do with rhs
+ if ($this->peek(self::$operatorString, $next) && self::$precedence[$next[1]] > self::$precedence[$m[1]]) {
+ $rhs = $this->expHelper($rhs, self::$precedence[$next[1]]);
+ }
+
+ $lhs = array('expression', $m[1], $lhs, $rhs, $whiteBefore, $whiteAfter);
+ $ss = $this->seek();
+
+ continue;
+ }
+
+ break;
+ }
+
+ $this->seek($ss);
+
+ return $lhs;
+ }
+
+ // consume a list of values for a property
+ public function propertyValue(&$value, $keyName = null) {
+ $values = array();
+
+ if ($keyName !== null) $this->env->currentProperty = $keyName;
+
+ $s = null;
+ while ($this->expressionList($v)) {
+ $values[] = $v;
+ $s = $this->seek();
+ if (!$this->literal(',')) break;
+ }
+
+ if ($s) $this->seek($s);
+
+ if ($keyName !== null) unset($this->env->currentProperty);
+
+ if (count($values) == 0) return false;
+
+ $value = lessc::compressList($values, ', ');
+ return true;
+ }
+
+ protected function parenValue(&$out) {
+ $s = $this->seek();
+
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "(") {
+ return false;
+ }
+
+ $inParens = $this->inParens;
+ if ($this->literal("(") &&
+ ($this->inParens = true) && $this->expression($exp) &&
+ $this->literal(")"))
+ {
+ $out = $exp;
+ $this->inParens = $inParens;
+ return true;
+ } else {
+ $this->inParens = $inParens;
+ $this->seek($s);
+ }
+
+ return false;
+ }
+
+ // a single value
+ protected function value(&$value) {
+ $s = $this->seek();
+
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "-") {
+ // negation
+ if ($this->literal("-", false) &&
+ (($this->variable($inner) && $inner = array("variable", $inner)) ||
+ $this->unit($inner) ||
+ $this->parenValue($inner)))
+ {
+ $value = array("unary", "-", $inner);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+ }
+
+ if ($this->parenValue($value)) return true;
+ if ($this->unit($value)) return true;
+ if ($this->color($value)) return true;
+ if ($this->func($value)) return true;
+ if ($this->string($value)) return true;
+
+ if ($this->keyword($word)) {
+ $value = array('keyword', $word);
+ return true;
+ }
+
+ // try a variable
+ if ($this->variable($var)) {
+ $value = array('variable', $var);
+ return true;
+ }
+
+ // unquote string (should this work on any type?
+ if ($this->literal("~") && $this->string($str)) {
+ $value = array("escape", $str);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ // css hack: \0
+ if ($this->literal('\\') && $this->match('([0-9]+)', $m)) {
+ $value = array('keyword', '\\'.$m[1]);
+ return true;
+ } else {
+ $this->seek($s);
+ }
+
+ return false;
+ }
+
+ // an import statement
+ protected function import(&$out) {
+ if (!$this->literal('@import')) return false;
+
+ // @import "something.css" media;
+ // @import url("something.css") media;
+ // @import url(something.css) media;
+
+ if ($this->propertyValue($value)) {
+ $out = array("import", $value);
+ return true;
+ }
+ }
+
+ protected function mediaQueryList(&$out) {
+ if ($this->genericList($list, "mediaQuery", ",", false)) {
+ $out = $list[2];
+ return true;
+ }
+ return false;
+ }
+
+ protected function mediaQuery(&$out) {
+ $s = $this->seek();
+
+ $expressions = null;
+ $parts = array();
+
+ if (($this->literal("only") && ($only = true) || $this->literal("not") && ($not = true) || true) && $this->keyword($mediaType)) {
+ $prop = array("mediaType");
+ if (isset($only)) $prop[] = "only";
+ if (isset($not)) $prop[] = "not";
+ $prop[] = $mediaType;
+ $parts[] = $prop;
+ } else {
+ $this->seek($s);
+ }
+
+
+ if (!empty($mediaType) && !$this->literal("and")) {
+ // ~
+ } else {
+ $this->genericList($expressions, "mediaExpression", "and", false);
+ if (is_array($expressions)) $parts = array_merge($parts, $expressions[2]);
+ }
+
+ if (count($parts) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ $out = $parts;
+ return true;
+ }
+
+ protected function mediaExpression(&$out) {
+ $s = $this->seek();
+ $value = null;
+ if ($this->literal("(") &&
+ $this->keyword($feature) &&
+ ($this->literal(":") && $this->expression($value) || true) &&
+ $this->literal(")"))
+ {
+ $out = array("mediaExp", $feature);
+ if ($value) $out[] = $value;
+ return true;
+ } elseif ($this->variable($variable)) {
+ $out = array('variable', $variable);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // an unbounded string stopped by $end
+ protected function openString($end, &$out, $nestingOpen=null, $rejectStrs = null) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ $stop = array("'", '"', "@{", $end);
+ $stop = array_map(array("lessc", "preg_quote"), $stop);
+ // $stop[] = self::$commentMulti;
+
+ if (!is_null($rejectStrs)) {
+ $stop = array_merge($stop, $rejectStrs);
+ }
+
+ $patt = '(.*?)('.implode("|", $stop).')';
+
+ $nestingLevel = 0;
+
+ $content = array();
+ while ($this->match($patt, $m, false)) {
+ if (!empty($m[1])) {
+ $content[] = $m[1];
+ if ($nestingOpen) {
+ $nestingLevel += substr_count($m[1], $nestingOpen);
+ }
+ }
+
+ $tok = $m[2];
+
+ $this->count-= strlen($tok);
+ if ($tok == $end) {
+ if ($nestingLevel == 0) {
+ break;
+ } else {
+ $nestingLevel--;
+ }
+ }
+
+ if (($tok == "'" || $tok == '"') && $this->string($str)) {
+ $content[] = $str;
+ continue;
+ }
+
+ if ($tok == "@{" && $this->interpolation($inter)) {
+ $content[] = $inter;
+ continue;
+ }
+
+ if (!empty($rejectStrs) && in_array($tok, $rejectStrs)) {
+ break;
+ }
+
+ $content[] = $tok;
+ $this->count+= strlen($tok);
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if (count($content) == 0) return false;
+
+ // trim the end
+ if (is_string(end($content))) {
+ $content[count($content) - 1] = rtrim(end($content));
+ }
+
+ $out = array("string", "", $content);
+ return true;
+ }
+
+ protected function string(&$out) {
+ $s = $this->seek();
+ if ($this->literal('"', false)) {
+ $delim = '"';
+ } elseif ($this->literal("'", false)) {
+ $delim = "'";
+ } else {
+ return false;
+ }
+
+ $content = array();
+
+ // look for either ending delim , escape, or string interpolation
+ $patt = '([^\n]*?)(@\{|\\\\|' .
+ lessc::preg_quote($delim).')';
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while ($this->match($patt, $m, false)) {
+ $content[] = $m[1];
+ if ($m[2] == "@{") {
+ $this->count -= strlen($m[2]);
+ if ($this->interpolation($inter, false)) {
+ $content[] = $inter;
+ } else {
+ $this->count += strlen($m[2]);
+ $content[] = "@{"; // ignore it
+ }
+ } elseif ($m[2] == '\\') {
+ $content[] = $m[2];
+ if ($this->literal($delim, false)) {
+ $content[] = $delim;
+ }
+ } else {
+ $this->count -= strlen($delim);
+ break; // delim
+ }
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+
+ if ($this->literal($delim)) {
+ $out = array("string", $delim, $content);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ protected function interpolation(&$out) {
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = true;
+
+ $s = $this->seek();
+ if ($this->literal("@{") &&
+ $this->openString("}", $interp, null, array("'", '"', ";")) &&
+ $this->literal("}", false))
+ {
+ $out = array("interpolate", $interp);
+ $this->eatWhiteDefault = $oldWhite;
+ if ($this->eatWhiteDefault) $this->whitespace();
+ return true;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+ $this->seek($s);
+ return false;
+ }
+
+ protected function unit(&$unit) {
+ // speed shortcut
+ if (isset($this->buffer[$this->count])) {
+ $char = $this->buffer[$this->count];
+ if (!ctype_digit($char) && $char != ".") return false;
+ }
+
+ if ($this->match('([0-9]+(?:\.[0-9]*)?|\.[0-9]+)([%a-zA-Z]+)?', $m)) {
+ $unit = array("number", $m[1], empty($m[2]) ? "" : $m[2]);
+ return true;
+ }
+ return false;
+ }
+
+ // a # color
+ protected function color(&$out) {
+ if ($this->match('(#(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3}))', $m)) {
+ if (strlen($m[1]) > 7) {
+ $out = array("string", "", array($m[1]));
+ } else {
+ $out = array("raw_color", $m[1]);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ // consume an argument definition list surrounded by ()
+ // each argument is a variable name with optional value
+ // or at the end a ... or a variable named followed by ...
+ // arguments are separated by , unless a ; is in the list, then ; is the
+ // delimiter.
+ protected function argumentDef(&$args, &$isVararg) {
+ $s = $this->seek();
+ if (!$this->literal('(')) return false;
+
+ $values = array();
+ $delim = ",";
+ $method = "expressionList";
+
+ $isVararg = false;
+ while (true) {
+ if ($this->literal("...")) {
+ $isVararg = true;
+ break;
+ }
+
+ if ($this->$method($value)) {
+ if ($value[0] == "variable") {
+ $arg = array("arg", $value[1]);
+ $ss = $this->seek();
+
+ if ($this->assign() && $this->$method($rhs)) {
+ $arg[] = $rhs;
+ } else {
+ $this->seek($ss);
+ if ($this->literal("...")) {
+ $arg[0] = "rest";
+ $isVararg = true;
+ }
+ }
+
+ $values[] = $arg;
+ if ($isVararg) break;
+ continue;
+ } else {
+ $values[] = array("lit", $value);
+ }
+ }
+
+
+ if (!$this->literal($delim)) {
+ if ($delim == "," && $this->literal(";")) {
+ // found new delim, convert existing args
+ $delim = ";";
+ $method = "propertyValue";
+
+ // transform arg list
+ if (isset($values[1])) { // 2 items
+ $newList = array();
+ foreach ($values as $i => $arg) {
+ switch($arg[0]) {
+ case "arg":
+ if ($i) {
+ $this->throwError("Cannot mix ; and , as delimiter types");
+ }
+ $newList[] = $arg[2];
+ break;
+ case "lit":
+ $newList[] = $arg[1];
+ break;
+ case "rest":
+ $this->throwError("Unexpected rest before semicolon");
+ }
+ }
+
+ $newList = array("list", ", ", $newList);
+
+ switch ($values[0][0]) {
+ case "arg":
+ $newArg = array("arg", $values[0][1], $newList);
+ break;
+ case "lit":
+ $newArg = array("lit", $newList);
+ break;
+ }
+
+ } elseif ($values) { // 1 item
+ $newArg = $values[0];
+ }
+
+ if ($newArg) {
+ $values = array($newArg);
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (!$this->literal(')')) {
+ $this->seek($s);
+ return false;
+ }
+
+ $args = $values;
+
+ return true;
+ }
+
+ // consume a list of tags
+ // this accepts a hanging delimiter
+ protected function tags(&$tags, $simple = false, $delim = ',') {
+ $tags = array();
+ while ($this->tag($tt, $simple)) {
+ $tags[] = $tt;
+ if (!$this->literal($delim)) break;
+ }
+ if (count($tags) == 0) return false;
+
+ return true;
+ }
+
+ // list of tags of specifying mixin path
+ // optionally separated by > (lazy, accepts extra >)
+ protected function mixinTags(&$tags) {
+ $tags = array();
+ while ($this->tag($tt, true)) {
+ $tags[] = $tt;
+ $this->literal(">");
+ }
+
+ if (count($tags) == 0) return false;
+
+ return true;
+ }
+
+ // a bracketed value (contained within in a tag definition)
+ protected function tagBracket(&$parts, &$hasExpression) {
+ // speed shortcut
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] != "[") {
+ return false;
+ }
+
+ $s = $this->seek();
+
+ $hasInterpolation = false;
+
+ if ($this->literal("[", false)) {
+ $attrParts = array("[");
+ // keyword, string, operator
+ while (true) {
+ if ($this->literal("]", false)) {
+ $this->count--;
+ break; // get out early
+ }
+
+ if ($this->match('\s+', $m)) {
+ $attrParts[] = " ";
+ continue;
+ }
+ if ($this->string($str)) {
+ // escape parent selector, (yuck)
+ foreach ($str[2] as &$chunk) {
+ $chunk = str_replace($this->lessc->parentSelector, "$&$", $chunk);
+ }
+
+ $attrParts[] = $str;
+ $hasInterpolation = true;
+ continue;
+ }
+
+ if ($this->keyword($word)) {
+ $attrParts[] = $word;
+ continue;
+ }
+
+ if ($this->interpolation($inter, false)) {
+ $attrParts[] = $inter;
+ $hasInterpolation = true;
+ continue;
+ }
+
+ // operator, handles attr namespace too
+ if ($this->match('[|-~\$\*\^=]+', $m)) {
+ $attrParts[] = $m[0];
+ continue;
+ }
+
+ break;
+ }
+
+ if ($this->literal("]", false)) {
+ $attrParts[] = "]";
+ foreach ($attrParts as $part) {
+ $parts[] = $part;
+ }
+ $hasExpression = $hasExpression || $hasInterpolation;
+ return true;
+ }
+ $this->seek($s);
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // a space separated list of selectors
+ protected function tag(&$tag, $simple = false) {
+ if ($simple)
+ $chars = '^@,:;{}\][>\(\) "\'';
+ else
+ $chars = '^@,;{}["\'';
+
+ $s = $this->seek();
+
+ $hasExpression = false;
+ $parts = array();
+ while ($this->tagBracket($parts, $hasExpression));
+
+ $oldWhite = $this->eatWhiteDefault;
+ $this->eatWhiteDefault = false;
+
+ while (true) {
+ if ($this->match('(['.$chars.'0-9]['.$chars.']*)', $m)) {
+ $parts[] = $m[1];
+ if ($simple) break;
+
+ while ($this->tagBracket($parts, $hasExpression));
+ continue;
+ }
+
+ if (isset($this->buffer[$this->count]) && $this->buffer[$this->count] == "@") {
+ if ($this->interpolation($interp)) {
+ $hasExpression = true;
+ $interp[2] = true; // don't unescape
+ $parts[] = $interp;
+ continue;
+ }
+
+ if ($this->literal("@")) {
+ $parts[] = "@";
+ continue;
+ }
+ }
+
+ if ($this->unit($unit)) { // for keyframes
+ $parts[] = $unit[1];
+ $parts[] = $unit[2];
+ continue;
+ }
+
+ break;
+ }
+
+ $this->eatWhiteDefault = $oldWhite;
+ if (!$parts) {
+ $this->seek($s);
+ return false;
+ }
+
+ if ($hasExpression) {
+ $tag = array("exp", array("string", "", $parts));
+ } else {
+ $tag = trim(implode($parts));
+ }
+
+ $this->whitespace();
+ return true;
+ }
+
+ // a css function
+ protected function func(&$func) {
+ $s = $this->seek();
+
+ if ($this->match('(%|[\w\-_][\w\-_:\.]+|[\w_])', $m) && $this->literal('(')) {
+ $fname = $m[1];
+
+ $sPreArgs = $this->seek();
+
+ $args = array();
+ while (true) {
+ $ss = $this->seek();
+ // this ugly nonsense is for ie filter properties
+ if ($this->keyword($name) && $this->literal('=') && $this->expressionList($value)) {
+ $args[] = array("string", "", array($name, "=", $value));
+ } else {
+ $this->seek($ss);
+ if ($this->expressionList($value)) {
+ $args[] = $value;
+ }
+ }
+
+ if (!$this->literal(',')) break;
+ }
+ $args = array('list', ',', $args);
+
+ if ($this->literal(')')) {
+ $func = array('function', $fname, $args);
+ return true;
+ } elseif ($fname == 'url') {
+ // couldn't parse and in url? treat as string
+ $this->seek($sPreArgs);
+ if ($this->openString(")", $string) && $this->literal(")")) {
+ $func = array('function', $fname, $string);
+ return true;
+ }
+ }
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ // consume a less variable
+ protected function variable(&$name) {
+ $s = $this->seek();
+ if ($this->literal($this->lessc->vPrefix, false) &&
+ ($this->variable($sub) || $this->keyword($name)))
+ {
+ if (!empty($sub)) {
+ $name = array('variable', $sub);
+ } else {
+ $name = $this->lessc->vPrefix.$name;
+ }
+ return true;
+ }
+
+ $name = null;
+ $this->seek($s);
+ return false;
+ }
+
+ /**
+ * Consume an assignment operator
+ * Can optionally take a name that will be set to the current property name
+ */
+ protected function assign($name = null) {
+ if ($name) $this->currentProperty = $name;
+ return $this->literal(':') || $this->literal('=');
+ }
+
+ // consume a keyword
+ protected function keyword(&$word) {
+ if ($this->match('([\w_\-\*!"][\w\-_"]*)', $m)) {
+ $word = $m[1];
+ return true;
+ }
+ return false;
+ }
+
+ // consume an end of statement delimiter
+ protected function end() {
+ if ($this->literal(';', false)) {
+ return true;
+ } elseif ($this->count == strlen($this->buffer) || $this->buffer[$this->count] == '}') {
+ // if there is end of file or a closing block next then we don't need a ;
+ return true;
+ }
+ return false;
+ }
+
+ protected function guards(&$guards) {
+ $s = $this->seek();
+
+ if (!$this->literal("when")) {
+ $this->seek($s);
+ return false;
+ }
+
+ $guards = array();
+
+ while ($this->guardGroup($g)) {
+ $guards[] = $g;
+ if (!$this->literal(",")) break;
+ }
+
+ if (count($guards) == 0) {
+ $guards = null;
+ $this->seek($s);
+ return false;
+ }
+
+ return true;
+ }
+
+ // a bunch of guards that are and'd together
+ // TODO rename to guardGroup
+ protected function guardGroup(&$guardGroup) {
+ $s = $this->seek();
+ $guardGroup = array();
+ while ($this->guard($guard)) {
+ $guardGroup[] = $guard;
+ if (!$this->literal("and")) break;
+ }
+
+ if (count($guardGroup) == 0) {
+ $guardGroup = null;
+ $this->seek($s);
+ return false;
+ }
+
+ return true;
+ }
+
+ protected function guard(&$guard) {
+ $s = $this->seek();
+ $negate = $this->literal("not");
+
+ if ($this->literal("(") && $this->expression($exp) && $this->literal(")")) {
+ $guard = $exp;
+ if ($negate) $guard = array("negate", $guard);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ /* raw parsing functions */
+
+ protected function literal($what, $eatWhitespace = null) {
+ if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
+
+ // shortcut on single letter
+ if (!isset($what[1]) && isset($this->buffer[$this->count])) {
+ if ($this->buffer[$this->count] == $what) {
+ if (!$eatWhitespace) {
+ $this->count++;
+ return true;
+ }
+ // goes below...
+ } else {
+ return false;
+ }
+ }
+
+ if (!isset(self::$literalCache[$what])) {
+ self::$literalCache[$what] = lessc::preg_quote($what);
+ }
+
+ return $this->match(self::$literalCache[$what], $m, $eatWhitespace);
+ }
+
+ protected function genericList(&$out, $parseItem, $delim="", $flatten=true) {
+ $s = $this->seek();
+ $items = array();
+ while ($this->$parseItem($value)) {
+ $items[] = $value;
+ if ($delim) {
+ if (!$this->literal($delim)) break;
+ }
+ }
+
+ if (count($items) == 0) {
+ $this->seek($s);
+ return false;
+ }
+
+ if ($flatten && count($items) == 1) {
+ $out = $items[0];
+ } else {
+ $out = array("list", $delim, $items);
+ }
+
+ return true;
+ }
+
+
+ // advance counter to next occurrence of $what
+ // $until - don't include $what in advance
+ // $allowNewline, if string, will be used as valid char set
+ protected function to($what, &$out, $until = false, $allowNewline = false) {
+ if (is_string($allowNewline)) {
+ $validChars = $allowNewline;
+ } else {
+ $validChars = $allowNewline ? "." : "[^\n]";
+ }
+ if (!$this->match('('.$validChars.'*?)'.lessc::preg_quote($what), $m, !$until)) return false;
+ if ($until) $this->count -= strlen($what); // give back $what
+ $out = $m[1];
+ return true;
+ }
+
+ // try to match something on head of buffer
+ protected function match($regex, &$out, $eatWhitespace = null) {
+ if ($eatWhitespace === null) $eatWhitespace = $this->eatWhiteDefault;
+
+ $r = '/'.$regex.($eatWhitespace && !$this->writeComments ? '\s*' : '').'/Ais';
+ if (preg_match($r, $this->buffer, $out, null, $this->count)) {
+ $this->count += strlen($out[0]);
+ if ($eatWhitespace && $this->writeComments) $this->whitespace();
+ return true;
+ }
+ return false;
+ }
+
+ // match some whitespace
+ protected function whitespace() {
+ if ($this->writeComments) {
+ $gotWhite = false;
+ while (preg_match(self::$whitePattern, $this->buffer, $m, null, $this->count)) {
+ if (isset($m[1]) && empty($this->seenComments[$this->count])) {
+ $this->append(array("comment", $m[1]));
+ $this->seenComments[$this->count] = true;
+ }
+ $this->count += strlen($m[0]);
+ $gotWhite = true;
+ }
+ return $gotWhite;
+ } else {
+ $this->match("", $m);
+ return strlen($m[0]) > 0;
+ }
+ }
+
+ // match something without consuming it
+ protected function peek($regex, &$out = null, $from=null) {
+ if (is_null($from)) $from = $this->count;
+ $r = '/'.$regex.'/Ais';
+ $result = preg_match($r, $this->buffer, $out, null, $from);
+
+ return $result;
+ }
+
+ // seek to a spot in the buffer or return where we are on no argument
+ protected function seek($where = null) {
+ if ($where === null) return $this->count;
+ else $this->count = $where;
+ return true;
+ }
+
+ /* misc functions */
+
+ public function throwError($msg = "parse error", $count = null) {
+ $count = is_null($count) ? $this->count : $count;
+
+ $line = $this->line +
+ substr_count(substr($this->buffer, 0, $count), "\n");
+
+ if (!empty($this->sourceName)) {
+ $loc = "$this->sourceName on line $line";
+ } else {
+ $loc = "line: $line";
+ }
+
+ // TODO this depends on $this->count
+ if ($this->peek("(.*?)(\n|$)", $m, $count)) {
+ throw new exception("$msg: failed at `$m[1]` $loc");
+ } else {
+ throw new exception("$msg: $loc");
+ }
+ }
+
+ protected function pushBlock($selectors=null, $type=null) {
+ $b = new stdclass;
+ $b->parent = $this->env;
+
+ $b->type = $type;
+ $b->id = self::$nextBlockId++;
+
+ $b->isVararg = false; // TODO: kill me from here
+ $b->tags = $selectors;
+
+ $b->props = array();
+ $b->children = array();
+
+ $this->env = $b;
+ return $b;
+ }
+
+ // push a block that doesn't multiply tags
+ protected function pushSpecialBlock($type) {
+ return $this->pushBlock(null, $type);
+ }
+
+ // append a property to the current block
+ protected function append($prop, $pos = null) {
+ if ($pos !== null) $prop[-1] = $pos;
+ $this->env->props[] = $prop;
+ }
+
+ // pop something off the stack
+ protected function pop() {
+ $old = $this->env;
+ $this->env = $this->env->parent;
+ return $old;
+ }
+
+ // remove comments from $text
+ // todo: make it work for all functions, not just url
+ protected function removeComments($text) {
+ $look = array(
+ 'url(', '//', '/*', '"', "'"
+ );
+
+ $out = '';
+ $min = null;
+ while (true) {
+ // find the next item
+ foreach ($look as $token) {
+ $pos = strpos($text, $token);
+ if ($pos !== false) {
+ if (!isset($min) || $pos < $min[1]) $min = array($token, $pos);
+ }
+ }
+
+ if (is_null($min)) break;
+
+ $count = $min[1];
+ $skip = 0;
+ $newlines = 0;
+ switch ($min[0]) {
+ case 'url(':
+ if (preg_match('/url\(.*?\)/', $text, $m, 0, $count))
+ $count += strlen($m[0]) - strlen($min[0]);
+ break;
+ case '"':
+ case "'":
+ if (preg_match('/'.$min[0].'.*?(?<!\\\\)'.$min[0].'/', $text, $m, 0, $count))
+ $count += strlen($m[0]) - 1;
+ break;
+ case '//':
+ $skip = strpos($text, "\n", $count);
+ if ($skip === false) $skip = strlen($text) - $count;
+ else $skip -= $count;
+ break;
+ case '/*':
+ if (preg_match('/\/\*.*?\*\//s', $text, $m, 0, $count)) {
+ $skip = strlen($m[0]);
+ $newlines = substr_count($m[0], "\n");
+ }
+ break;
+ }
+
+ if ($skip == 0) $count += strlen($min[0]);
+
+ $out .= substr($text, 0, $count).str_repeat("\n", $newlines);
+ $text = substr($text, $count + $skip);
+
+ $min = null;
+ }
+
+ return $out.$text;
+ }
+
+}
+
+class lessc_formatter_classic {
+ public $indentChar = " ";
+
+ public $break = "\n";
+ public $open = " {";
+ public $close = "}";
+ public $selectorSeparator = ", ";
+ public $assignSeparator = ":";
+
+ public $openSingle = " { ";
+ public $closeSingle = " }";
+
+ public $disableSingle = false;
+ public $breakSelectors = false;
+
+ public $compressColors = false;
+
+ public function __construct() {
+ $this->indentLevel = 0;
+ }
+
+ public function indentStr($n = 0) {
+ return str_repeat($this->indentChar, max($this->indentLevel + $n, 0));
+ }
+
+ public function property($name, $value) {
+ return $name . $this->assignSeparator . $value . ";";
+ }
+
+ protected function isEmpty($block) {
+ if (empty($block->lines)) {
+ foreach ($block->children as $child) {
+ if (!$this->isEmpty($child)) return false;
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ public function block($block) {
+ if ($this->isEmpty($block)) return;
+
+ $inner = $pre = $this->indentStr();
+
+ $isSingle = !$this->disableSingle &&
+ is_null($block->type) && count($block->lines) == 1;
+
+ if (!empty($block->selectors)) {
+ $this->indentLevel++;
+
+ if ($this->breakSelectors) {
+ $selectorSeparator = $this->selectorSeparator . $this->break . $pre;
+ } else {
+ $selectorSeparator = $this->selectorSeparator;
+ }
+
+ echo $pre .
+ implode($selectorSeparator, $block->selectors);
+ if ($isSingle) {
+ echo $this->openSingle;
+ $inner = "";
+ } else {
+ echo $this->open . $this->break;
+ $inner = $this->indentStr();
+ }
+
+ }
+
+ if (!empty($block->lines)) {
+ $glue = $this->break.$inner;
+ echo $inner . implode($glue, $block->lines);
+ if (!$isSingle && !empty($block->children)) {
+ echo $this->break;
+ }
+ }
+
+ foreach ($block->children as $child) {
+ $this->block($child);
+ }
+
+ if (!empty($block->selectors)) {
+ if (!$isSingle && empty($block->children)) echo $this->break;
+
+ if ($isSingle) {
+ echo $this->closeSingle . $this->break;
+ } else {
+ echo $pre . $this->close . $this->break;
+ }
+
+ $this->indentLevel--;
+ }
+ }
+}
+
+class lessc_formatter_compressed extends lessc_formatter_classic {
+ public $disableSingle = true;
+ public $open = "{";
+ public $selectorSeparator = ",";
+ public $assignSeparator = ":";
+ public $break = "";
+ public $compressColors = true;
+
+ public function indentStr($n = 0) {
+ return "";
+ }
+}
+
+class lessc_formatter_lessjs extends lessc_formatter_classic {
+ public $disableSingle = true;
+ public $breakSelectors = true;
+ public $assignSeparator = ": ";
+ public $selectorSeparator = ",";
+}
+
+
diff --git a/vendor/leafo/lessphp/lessify b/vendor/leafo/lessphp/lessify
new file mode 100644
index 00000000..becc5388
--- /dev/null
+++ b/vendor/leafo/lessphp/lessify
@@ -0,0 +1,23 @@
+#!/usr/bin/php
+<?php
+
+if (php_sapi_name() != "cli") {
+ err($fa.$argv[0]." must be run in the command line.");
+ exit(1);
+}
+$exe = array_shift($argv); // remove filename
+
+if (!$fname = array_shift($argv)) {
+ exit("Usage: ".$exe." input-file\n");
+}
+
+require "lessify.inc.php";
+
+try {
+ $parser = new lessify($fname);
+ echo $parser->parse();
+} catch (exception $e) {
+ exit("Fatal error: ".$e->getMessage()."\n");
+}
+
+
diff --git a/vendor/leafo/lessphp/lessify.inc.php b/vendor/leafo/lessphp/lessify.inc.php
new file mode 100644
index 00000000..91c14423
--- /dev/null
+++ b/vendor/leafo/lessphp/lessify.inc.php
@@ -0,0 +1,447 @@
+<?php
+/**
+ * lessify
+ * Convert a css file into a less file
+ * http://leafo.net/lessphp
+ * Copyright 2010, leaf corcoran <leafot@gmail.com>
+ *
+ * WARNING: THIS DOES NOT WORK ANYMORE. NEEDS TO BE UPDATED FOR
+ * LATEST VERSION OF LESSPHP.
+ *
+ */
+
+require "lessc.inc.php";
+
+//
+// check if the merge during mixin is overwriting values. should or should it not?
+//
+
+//
+// 1. split apart class tags
+//
+
+class easyparse {
+ var $buffer;
+ var $count;
+
+ function __construct($str) {
+ $this->count = 0;
+ $this->buffer = trim($str);
+ }
+
+ function seek($where = null) {
+ if ($where === null) return $this->count;
+ else $this->count = $where;
+ return true;
+ }
+
+ function preg_quote($what) {
+ return preg_quote($what, '/');
+ }
+
+ function match($regex, &$out, $eatWhitespace = true) {
+ $r = '/'.$regex.($eatWhitespace ? '\s*' : '').'/Ais';
+ if (preg_match($r, $this->buffer, $out, null, $this->count)) {
+ $this->count += strlen($out[0]);
+ return true;
+ }
+ return false;
+ }
+
+ function literal($what, $eatWhitespace = true) {
+ // this is here mainly prevent notice from { } string accessor
+ if ($this->count >= strlen($this->buffer)) return false;
+
+ // shortcut on single letter
+ if (!$eatWhitespace and strlen($what) == 1) {
+ if ($this->buffer{$this->count} == $what) {
+ $this->count++;
+ return true;
+ }
+ else return false;
+ }
+
+ return $this->match($this->preg_quote($what), $m, $eatWhitespace);
+ }
+
+}
+
+class tagparse extends easyparse {
+ static private $combinators = null;
+ static private $match_opts = null;
+
+ function parse() {
+ if (empty(self::$combinators)) {
+ self::$combinators = '('.implode('|', array_map(array($this, 'preg_quote'),
+ array('+', '>', '~'))).')';
+ self::$match_opts = '('.implode('|', array_map(array($this, 'preg_quote'),
+ array('=', '~=', '|=', '$=', '*='))).')';
+ }
+
+ // crush whitespace
+ $this->buffer = preg_replace('/\s+/', ' ', $this->buffer).' ';
+
+ $tags = array();
+ while ($this->tag($t)) $tags[] = $t;
+
+ return $tags;
+ }
+
+ static function compileString($string) {
+ list(, $delim, $str) = $string;
+ $str = str_replace($delim, "\\".$delim, $str);
+ $str = str_replace("\n", "\\\n", $str);
+ return $delim.$str.$delim;
+ }
+
+ static function compilePaths($paths) {
+ return implode(', ', array_map(array('self', 'compilePath'), $paths));
+ }
+
+ // array of tags
+ static function compilePath($path) {
+ return implode(' ', array_map(array('self', 'compileTag'), $path));
+ }
+
+
+ static function compileTag($tag) {
+ ob_start();
+ if (isset($tag['comb'])) echo $tag['comb']." ";
+ if (isset($tag['front'])) echo $tag['front'];
+ if (isset($tag['attr'])) {
+ echo '['.$tag['attr'];
+ if (isset($tag['op'])) {
+ echo $tag['op'].$tag['op_value'];
+ }
+ echo ']';
+ }
+ return ob_get_clean();
+ }
+
+ function string(&$out) {
+ $s = $this->seek();
+
+ if ($this->literal('"')) {
+ $delim = '"';
+ } elseif ($this->literal("'")) {
+ $delim = "'";
+ } else {
+ return false;
+ }
+
+ while (true) {
+ // step through letters looking for either end or escape
+ $buff = "";
+ $escapeNext = false;
+ $finished = false;
+ for ($i = $this->count; $i < strlen($this->buffer); $i++) {
+ $char = $this->buffer[$i];
+ switch ($char) {
+ case $delim:
+ if ($escapeNext) {
+ $buff .= $char;
+ $escapeNext = false;
+ break;
+ }
+ $finished = true;
+ break 2;
+ case "\\":
+ if ($escapeNext) {
+ $buff .= $char;
+ $escapeNext = false;
+ } else {
+ $escapeNext = true;
+ }
+ break;
+ case "\n":
+ if (!$escapeNext) {
+ break 3;
+ }
+
+ $buff .= $char;
+ $escapeNext = false;
+ break;
+ default:
+ if ($escapeNext) {
+ $buff .= "\\";
+ $escapeNext = false;
+ }
+ $buff .= $char;
+ }
+ }
+ if (!$finished) break;
+ $out = array('string', $delim, $buff);
+ $this->seek($i+1);
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ function tag(&$out) {
+ $s = $this->seek();
+ $tag = array();
+ if ($this->combinator($op)) $tag['comb'] = $op;
+
+ if (!$this->match('(.*?)( |$|\[|'.self::$combinators.')', $match)) {
+ $this->seek($s);
+ return false;
+ }
+
+ if (!empty($match[3])) {
+ // give back combinator
+ $this->count-=strlen($match[3]);
+ }
+
+ if (!empty($match[1])) $tag['front'] = $match[1];
+
+ if ($match[2] == '[') {
+ if ($this->ident($i)) {
+ $tag['attr'] = $i;
+
+ if ($this->match(self::$match_opts, $m) && $this->value($v)) {
+ $tag['op'] = $m[1];
+ $tag['op_value'] = $v;
+ }
+
+ if ($this->literal(']')) {
+ $out = $tag;
+ return true;
+ }
+ }
+ } elseif (isset($tag['front'])) {
+ $out = $tag;
+ return true;
+ }
+
+ $this->seek($s);
+ return false;
+ }
+
+ function ident(&$out) {
+ // [-]?{nmstart}{nmchar}*
+ // nmstart: [_a-z]|{nonascii}|{escape}
+ // nmchar: [_a-z0-9-]|{nonascii}|{escape}
+ if ($this->match('(-?[_a-z][_\w]*)', $m)) {
+ $out = $m[1];
+ return true;
+ }
+ return false;
+ }
+
+ function value(&$out) {
+ if ($this->string($str)) {
+ $out = $this->compileString($str);
+ return true;
+ } elseif ($this->ident($id)) {
+ $out = $id;
+ return true;
+ }
+ return false;
+ }
+
+
+ function combinator(&$op) {
+ if ($this->match(self::$combinators, $m)) {
+ $op = $m[1];
+ return true;
+ }
+ return false;
+ }
+}
+
+class nodecounter {
+ var $count = 0;
+ var $children = array();
+
+ var $name;
+ var $child_blocks;
+ var $the_block;
+
+ function __construct($name) {
+ $this->name = $name;
+ }
+
+ function dump($stack = null) {
+ if (is_null($stack)) $stack = array();
+ $stack[] = $this->getName();
+ echo implode(' -> ', $stack)." ($this->count)\n";
+ foreach ($this->children as $child) {
+ $child->dump($stack);
+ }
+ }
+
+ static function compileProperties($c, $block) {
+ foreach($block as $name => $value) {
+ if ($c->isProperty($name, $value)) {
+ echo $c->compileProperty($name, $value)."\n";
+ }
+ }
+ }
+
+ function compile($c, $path = null) {
+ if (is_null($path)) $path = array();
+ $path[] = $this->name;
+
+ $isVisible = !is_null($this->the_block) || !is_null($this->child_blocks);
+
+ if ($isVisible) {
+ echo $c->indent(implode(' ', $path).' {');
+ $c->indentLevel++;
+ $path = array();
+
+ if ($this->the_block) {
+ $this->compileProperties($c, $this->the_block);
+ }
+
+ if ($this->child_blocks) {
+ foreach ($this->child_blocks as $block) {
+ echo $c->indent(tagparse::compilePaths($block['__tags']).' {');
+ $c->indentLevel++;
+ $this->compileProperties($c, $block);
+ $c->indentLevel--;
+ echo $c->indent('}');
+ }
+ }
+ }
+
+ // compile child nodes
+ foreach($this->children as $node) {
+ $node->compile($c, $path);
+ }
+
+ if ($isVisible) {
+ $c->indentLevel--;
+ echo $c->indent('}');
+ }
+
+ }
+
+ function getName() {
+ if (is_null($this->name)) return "[root]";
+ else return $this->name;
+ }
+
+ function getNode($name) {
+ if (!isset($this->children[$name])) {
+ $this->children[$name] = new nodecounter($name);
+ }
+
+ return $this->children[$name];
+ }
+
+ function findNode($path) {
+ $current = $this;
+ for ($i = 0; $i < count($path); $i++) {
+ $t = tagparse::compileTag($path[$i]);
+ $current = $current->getNode($t);
+ }
+
+ return $current;
+ }
+
+ function addBlock($path, $block) {
+ $node = $this->findNode($path);
+ if (!is_null($node->the_block)) throw new exception("can this happen?");
+
+ unset($block['__tags']);
+ $node->the_block = $block;
+ }
+
+ function addToNode($path, $block) {
+ $node = $this->findNode($path);
+ $node->child_blocks[] = $block;
+ }
+}
+
+/**
+ * create a less file from a css file by combining blocks where appropriate
+ */
+class lessify extends lessc {
+ public function dump() {
+ print_r($this->env);
+ }
+
+ public function parse($str = null) {
+ $this->prepareParser($str ? $str : $this->buffer);
+ while (false !== $this->parseChunk());
+
+ $root = new nodecounter(null);
+
+ // attempt to preserve some of the block order
+ $order = array();
+
+ $visitedTags = array();
+ foreach (end($this->env) as $name => $block) {
+ if (!$this->isBlock($name, $block)) continue;
+ if (isset($visitedTags[$name])) continue;
+
+ foreach ($block['__tags'] as $t) {
+ $visitedTags[$t] = true;
+ }
+
+ // skip those with more than 1
+ if (count($block['__tags']) == 1) {
+ $p = new tagparse(end($block['__tags']));
+ $path = $p->parse();
+ $root->addBlock($path, $block);
+ $order[] = array('compressed', $path, $block);
+ continue;
+ } else {
+ $common = null;
+ $paths = array();
+ foreach ($block['__tags'] as $rawtag) {
+ $p = new tagparse($rawtag);
+ $paths[] = $path = $p->parse();
+ if (is_null($common)) $common = $path;
+ else {
+ $new_common = array();
+ foreach ($path as $tag) {
+ $head = array_shift($common);
+ if ($tag == $head) {
+ $new_common[] = $head;
+ } else break;
+ }
+ $common = $new_common;
+ if (empty($common)) {
+ // nothing in common
+ break;
+ }
+ }
+ }
+
+ if (!empty($common)) {
+ $new_paths = array();
+ foreach ($paths as $p) $new_paths[] = array_slice($p, count($common));
+ $block['__tags'] = $new_paths;
+ $root->addToNode($common, $block);
+ $order[] = array('compressed', $common, $block);
+ continue;
+ }
+
+ }
+
+ $order[] = array('none', $block['__tags'], $block);
+ }
+
+
+ $compressed = $root->children;
+ foreach ($order as $item) {
+ list($type, $tags, $block) = $item;
+ if ($type == 'compressed') {
+ $top = tagparse::compileTag(reset($tags));
+ if (isset($compressed[$top])) {
+ $compressed[$top]->compile($this);
+ unset($compressed[$top]);
+ }
+ } else {
+ echo $this->indent(implode(', ', $tags).' {');
+ $this->indentLevel++;
+ nodecounter::compileProperties($this, $block);
+ $this->indentLevel--;
+ echo $this->indent('}');
+ }
+ }
+ }
+}
diff --git a/vendor/leafo/lessphp/package.sh b/vendor/leafo/lessphp/package.sh
new file mode 100644
index 00000000..f0888f12
--- /dev/null
+++ b/vendor/leafo/lessphp/package.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# creates tar.gz for current version
+
+VERSION=`./plessc -v | sed -n 's/^v\(.*\)$/\1/p'`
+OUT_DIR="tmp/lessphp"
+TMP=`dirname $OUT_DIR`
+
+mkdir -p $OUT_DIR
+tar -c `git ls-files` | tar -C $OUT_DIR -x
+
+rm $OUT_DIR/.gitignore
+rm $OUT_DIR/package.sh
+rm $OUT_DIR/lessify
+rm $OUT_DIR/lessify.inc.php
+
+OUT_NAME="lessphp-$VERSION.tar.gz"
+tar -czf $OUT_NAME -C $TMP lessphp/
+echo "Wrote $OUT_NAME"
+
+rm -r $TMP
+
+
+echo
+echo "Don't forget to"
+echo "* Update the version in lessc.inc.php (two places)"
+echo "* Update the version in the README.md"
+echo "* Update the version in docs.md (two places)"
+echo "* Update the version in LICENSE"
+echo "* Update @current_version in site.moon"
+echo "* Add entry to feed.moon for changelog"
+echo "* Update the -New- area on homepage with date and features"
+echo
+
+
diff --git a/vendor/leafo/lessphp/plessc b/vendor/leafo/lessphp/plessc
new file mode 100644
index 00000000..1871dc77
--- /dev/null
+++ b/vendor/leafo/lessphp/plessc
@@ -0,0 +1,250 @@
+#!/usr/bin/env php
+<?php
+// Command line utility to compile LESS to STDOUT
+// Leaf Corcoran <leafot@gmail.com>, 2013
+
+$exe = array_shift($argv); // remove filename
+
+$HELP = <<<EOT
+Usage: $exe [options] input-file [output-file]
+
+Options include:
+
+ -h, --help Show this message
+ -v Print the version
+ -f=format Set the output format, includes "default", "compressed"
+ -c Keep /* */ comments in output
+ -r Read from STDIN instead of input-file
+ -w Watch input-file, and compile to output-file if it is changed
+ -T Dump formatted parse tree
+ -X Dump raw parse tree
+
+
+EOT;
+
+$opts = getopt('hvrwncXTf:', array('help'));
+while (count($argv) > 0 && preg_match('/^-([-hvrwncXT]$|[f]=)/', $argv[0])) {
+ array_shift($argv);
+}
+
+function has() {
+ global $opts;
+ foreach (func_get_args() as $arg) {
+ if (isset($opts[$arg])) return true;
+ }
+ return false;
+}
+
+if (has("h", "help")) {
+ exit($HELP);
+}
+
+error_reporting(E_ALL);
+$path = realpath(dirname(__FILE__)).'/';
+
+require $path."lessc.inc.php";
+
+$VERSION = lessc::$VERSION;
+
+$fa = "Fatal Error: ";
+function err($msg) {
+ fwrite(STDERR, $msg."\n");
+}
+
+if (php_sapi_name() != "cli") {
+ err($fa.$argv[0]." must be run in the command line.");
+ exit(1);
+}
+
+function make_less($fname = null) {
+ global $opts;
+ $l = new lessc($fname);
+
+ if (has("f")) {
+ $format = $opts["f"];
+ if ($format != "default") $l->setFormatter($format);
+ }
+
+ if (has("c")) {
+ $l->setPreserveComments(true);
+ }
+
+ return $l;
+}
+
+function process($data, $import = null) {
+ global $fa;
+
+ $l = make_less();
+ if ($import) $l->importDir = $import;
+
+ try {
+ echo $l->parse($data);
+ exit(0);
+ } catch (exception $ex) {
+ err($fa."\n".str_repeat('=', 20)."\n".
+ $ex->getMessage());
+ exit(1);
+ }
+}
+
+if (has("v")) {
+ exit($VERSION."\n");
+}
+
+if (has("r")) {
+ if (!empty($argv)) {
+ $data = $argv[0];
+ } else {
+ $data = "";
+ while (!feof(STDIN)) {
+ $data .= fread(STDIN, 8192);
+ }
+ }
+ exit(process($data));
+}
+
+if (has("w")) {
+ // need two files
+ if (!is_file($in = array_shift($argv)) ||
+ null == $out = array_shift($argv))
+ {
+ err($fa.$exe." -w infile outfile");
+ exit(1);
+ }
+
+ echo "Watching ".$in.
+ (has("n") ? ' with notifications' : '').
+ ", press Ctrl + c to exit.\n";
+
+ $cache = $in;
+ $last_action = 0;
+ while (true) {
+ clearstatcache();
+
+ // check if anything has changed since last fail
+ $updated = false;
+ if (is_array($cache)) {
+ foreach ($cache['files'] as $fname=>$_) {
+ if (filemtime($fname) > $last_action) {
+ $updated = true;
+ break;
+ }
+ }
+ } else $updated = true;
+
+ // try to compile it
+ if ($updated) {
+ $last_action = time();
+
+ try {
+ $cache = lessc::cexecute($cache);
+ echo "Writing updated file: ".$out."\n";
+ if (!file_put_contents($out, $cache['compiled'])) {
+ err($fa."Could not write to file ".$out);
+ exit(1);
+ }
+ } catch (exception $ex) {
+ echo "\nFatal Error:\n".str_repeat('=', 20)."\n".
+ $ex->getMessage()."\n\n";
+
+ if (has("n")) {
+ `notify-send -u critical "compile failed" "{$ex->getMessage()}"`;
+ }
+ }
+ }
+
+ sleep(1);
+ }
+ exit(0);
+}
+
+if (!$fname = array_shift($argv)) {
+ echo $HELP;
+ exit(1);
+}
+
+function dumpValue($node, $depth = 0) {
+ if (is_object($node)) {
+ $indent = str_repeat(" ", $depth);
+ $out = array();
+ foreach ($node->props as $prop) {
+ $out[] = $indent . dumpValue($prop, $depth + 1);
+ }
+ $out = implode("\n", $out);
+ if (!empty($node->tags)) {
+ $out = "+ ".implode(", ", $node->tags)."\n".$out;
+ }
+ return $out;
+ } elseif (is_array($node)) {
+ if (empty($node)) return "[]";
+ $type = $node[0];
+ if ($type == "block")
+ return dumpValue($node[1], $depth);
+
+ $out = array();
+ foreach ($node as $value) {
+ $out[] = dumpValue($value, $depth);
+ }
+ return "{ ".implode(", ", $out)." }";
+ } else {
+ if (is_string($node) && preg_match("/[\s,]/", $node)) {
+ return '"'.$node.'"';
+ }
+ return $node; // normal value
+ }
+}
+
+
+function stripValue($o, $toStrip) {
+ if (is_array($o) || is_object($o)) {
+ $isObject = is_object($o);
+ $o = (array)$o;
+ foreach ($toStrip as $removeKey) {
+ if (!empty($o[$removeKey])) {
+ $o[$removeKey] = "*stripped*";
+ }
+ }
+
+ foreach ($o as $k => $v) {
+ $o[$k] = stripValue($v, $toStrip);
+ }
+
+ if ($isObject) {
+ $o = (object)$o;
+ }
+ }
+
+ return $o;
+}
+
+function dumpWithoutParent($o, $alsoStrip=array()) {
+ $toStrip = array_merge(array("parent"), $alsoStrip);
+ print_r(stripValue($o, $toStrip));
+}
+
+try {
+ $less = make_less($fname);
+ if (has("T", "X")) {
+ $parser = new lessc_parser($less, $fname);
+ $tree = $parser->parse(file_get_contents($fname));
+ if (has("X"))
+ $out = print_r($tree, 1);
+ else
+ $out = dumpValue($tree)."\n";
+ } else {
+ $out = $less->parse();
+ }
+
+ if (!$fout = array_shift($argv)) {
+ echo $out;
+ } else {
+ file_put_contents($fout, $out);
+ }
+
+} catch (exception $ex) {
+ err($fa.$ex->getMessage());
+ exit(1);
+}
+
+?>
diff --git a/vendor/leafo/lessphp/tests/ApiTest.php b/vendor/leafo/lessphp/tests/ApiTest.php
new file mode 100644
index 00000000..fe1bbffa
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/ApiTest.php
@@ -0,0 +1,196 @@
+<?php
+
+require_once __DIR__ . "/../lessc.inc.php";
+
+class ApiTest extends PHPUnit_Framework_TestCase {
+ public function setUp() {
+ $this->less = new lessc();
+ $this->less->importDir = array(__DIR__ . "/inputs/test-imports");
+ }
+
+ public function testPreserveComments() {
+ $input = <<<EOD
+// what is going on?
+
+/** what the heck **/
+
+/**
+
+Here is a block comment
+
+**/
+
+
+// this is a comment
+
+/*hello*/div /*yeah*/ { //surew
+ border: 1px solid red; // world
+ /* comment above the first occurrence of a duplicated rule */
+ color: url('http://mage-page.com');
+ string: "hello /* this is not a comment */";
+ world: "// neither is this";
+ /* comment above the second occurrence of a duplicated rule */
+ color: url('http://mage-page.com');
+ string: 'hello /* this is not a comment */' /*what if this is a comment */;
+ world: '// neither is this' // hell world;
+ ;
+ /* duplicate comments are retained */
+ /* duplicate comments are retained */
+ what-ever: 100px;
+ background: url(/*this is not a comment?*/); // uhh what happens here
+}
+EOD;
+
+
+ $outputWithComments = <<<EOD
+/** what the heck **/
+/**
+
+Here is a block comment
+
+**/
+/*hello*/
+/*yeah*/
+div /*yeah*/ {
+ border: 1px solid red;
+ /* comment above the first occurrence of a duplicated rule */
+ /* comment above the second occurrence of a duplicated rule */
+ color: url('http://mage-page.com');
+ string: "hello /* this is not a comment */";
+ world: "// neither is this";
+ /*what if this is a comment */
+ string: 'hello /* this is not a comment */';
+ world: '// neither is this';
+ /* duplicate comments are retained */
+ /* duplicate comments are retained */
+ what-ever: 100px;
+ /*this is not a comment?*/
+ background: url();
+}
+EOD;
+
+ $outputWithoutComments = <<<EOD
+div {
+ border: 1px solid red;
+ color: url('http://mage-page.com');
+ string: "hello /* this is not a comment */";
+ world: "// neither is this";
+ string: 'hello /* this is not a comment */';
+ world: '// neither is this';
+ what-ever: 100px;
+ background: url(/*this is not a comment?*/);
+}
+EOD;
+
+ $this->assertEquals($this->compile($input), trim($outputWithoutComments));
+ $this->less->setPreserveComments(true);
+ $this->assertEquals($this->compile($input), trim($outputWithComments));
+ }
+
+ public function testOldInterface() {
+ $this->less = new lessc(__DIR__ . "/inputs/hi.less");
+ $out = $this->less->parse(array("hello" => "10px"));
+ $this->assertEquals(trim($out), trim('
+div:before {
+ content: "hi!";
+}'));
+
+ }
+
+ public function testInjectVars() {
+ $out = $this->less->parse(".magic { color: @color; width: @base - 200; }",
+ array(
+ 'color' => 'red',
+ 'base' => '960px'
+ ));
+
+ $this->assertEquals(trim($out), trim("
+.magic {
+ color: red;
+ width: 760px;
+}"));
+
+ }
+
+ public function testDisableImport() {
+ $this->less->importDisabled = true;
+ $this->assertEquals(
+ "/* import disabled */",
+ $this->compile("@import 'file3';"));
+ }
+
+ public function testUserFunction() {
+ $this->less->registerFunction("add-two", function($list) {
+ list($a, $b) = $list[2];
+ return $a[1] + $b[1];
+ });
+
+ $this->assertEquals(
+ $this->compile("result: add-two(10, 20);"),
+ "result: 30;");
+
+ return $this->less;
+ }
+
+ /**
+ * @depends testUserFunction
+ */
+ public function testUnregisterFunction($less) {
+ $less->unregisterFunction("add-two");
+
+ $this->assertEquals(
+ $this->compile("result: add-two(10, 20);"),
+ "result: add-two(10,20);");
+ }
+
+
+
+ public function testFormatters() {
+ $src = "
+ div, pre {
+ color: blue;
+ span, .big, hello.world {
+ height: 20px;
+ color:#ffffff + #000;
+ }
+ }";
+
+ $this->less->setFormatter("compressed");
+ $this->assertEquals(
+ $this->compile($src), "div,pre{color:blue;}div span,div .big,div hello.world,pre span,pre .big,pre hello.world{height:20px;color:#fff;}");
+
+ // TODO: fix the output order of tags
+ $this->less->setFormatter("lessjs");
+ $this->assertEquals(
+ $this->compile($src),
+"div,
+pre {
+ color: blue;
+}
+div span,
+div .big,
+div hello.world,
+pre span,
+pre .big,
+pre hello.world {
+ height: 20px;
+ color: #ffffff;
+}");
+
+ $this->less->setFormatter("classic");
+ $this->assertEquals(
+ $this->compile($src),
+trim("div, pre { color:blue; }
+div span, div .big, div hello.world, pre span, pre .big, pre hello.world {
+ height:20px;
+ color:#ffffff;
+}
+"));
+
+ }
+
+ public function compile($str) {
+ return trim($this->less->parse($str));
+ }
+
+}
diff --git a/vendor/leafo/lessphp/tests/ErrorHandlingTest.php b/vendor/leafo/lessphp/tests/ErrorHandlingTest.php
new file mode 100644
index 00000000..de02f065
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/ErrorHandlingTest.php
@@ -0,0 +1,81 @@
+<?php
+require_once __DIR__ . "/../lessc.inc.php";
+
+class ErrorHandlingTest extends PHPUnit_Framework_TestCase {
+ public function setUp() {
+ $this->less = new lessc();
+ }
+
+ public function compile() {
+ $source = join("\n", func_get_args());
+ return $this->less->compile($source);
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage .parametric-mixin is undefined
+ */
+ public function testRequiredParametersMissing() {
+ $this->compile(
+ '.parametric-mixin (@a, @b) { a: @a; b: @b; }',
+ '.selector { .parametric-mixin(12px); }'
+ );
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage .parametric-mixin is undefined
+ */
+ public function testTooManyParameters() {
+ $this->compile(
+ '.parametric-mixin (@a, @b) { a: @a; b: @b; }',
+ '.selector { .parametric-mixin(12px, 13px, 14px); }'
+ );
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage unrecognised input
+ */
+ public function testRequiredArgumentsMissing() {
+ $this->compile('.selector { rule: e(); }');
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage variable @missing is undefined
+ */
+ public function testVariableMissing() {
+ $this->compile('.selector { rule: @missing; }');
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage .missing-mixin is undefined
+ */
+ public function testMixinMissing() {
+ $this->compile('.selector { .missing-mixin; }');
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage .flipped is undefined
+ */
+ public function testGuardUnmatchedValue() {
+ $this->compile(
+ '.flipped(@x) when (@x =< 10) { rule: value; }',
+ '.selector { .flipped(12); }'
+ );
+ }
+
+ /**
+ * @expectedException Exception
+ * @expectedExceptionMessage .colors-only is undefined
+ */
+ public function testGuardUnmatchedType() {
+ $this->compile(
+ '.colors-only(@x) when (iscolor(@x)) { rule: value; }',
+ '.selector { .colors-only("string value"); }'
+ );
+ }
+}
diff --git a/vendor/leafo/lessphp/tests/InputTest.php b/vendor/leafo/lessphp/tests/InputTest.php
new file mode 100644
index 00000000..32db95bc
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/InputTest.php
@@ -0,0 +1,89 @@
+<?php
+
+require_once __DIR__ . "/../lessc.inc.php";
+
+// Runs all the tests in inputs/ and compares their output to ouputs/
+
+function _dump($value) {
+ fwrite(STDOUT, print_r($value, true));
+}
+
+function _quote($str) {
+ return preg_quote($str, "/");
+}
+
+class InputTest extends PHPUnit_Framework_TestCase {
+ protected static $importDirs = array("inputs/test-imports");
+
+ protected static $testDirs = array(
+ "inputs" => "outputs",
+ "inputs_lessjs" => "outputs_lessjs",
+ );
+
+ public function setUp() {
+ $this->less = new lessc();
+ $this->less->importDir = array_map(function($path) {
+ return __DIR__ . "/" . $path;
+ }, self::$importDirs);
+ }
+
+ /**
+ * @dataProvider fileNameProvider
+ */
+ public function testInputFile($inFname) {
+ if ($pattern = getenv("BUILD")) {
+ return $this->buildInput($inFname);
+ }
+
+ $outFname = self::outputNameFor($inFname);
+
+ if (!is_readable($outFname)) {
+ $this->fail("$outFname is missing, ".
+ "consider building tests with BUILD=true");
+ }
+
+ $input = file_get_contents($inFname);
+ $output = file_get_contents($outFname);
+
+ $this->assertEquals($output, $this->less->parse($input));
+ }
+
+ public function fileNameProvider() {
+ return array_map(function($a) { return array($a); },
+ self::findInputNames());
+ }
+
+ // only run when env is set
+ public function buildInput($inFname) {
+ $css = $this->less->parse(file_get_contents($inFname));
+ file_put_contents(self::outputNameFor($inFname), $css);
+ }
+
+ static public function findInputNames($pattern="*.less") {
+ $files = array();
+ foreach (self::$testDirs as $inputDir => $outputDir) {
+ $files = array_merge($files, glob(__DIR__ . "/" . $inputDir . "/" . $pattern));
+ }
+
+ return array_filter($files, "is_file");
+ }
+
+ static public function outputNameFor($input) {
+ $front = _quote(__DIR__ . "/");
+ $out = preg_replace("/^$front/", "", $input);
+
+ foreach (self::$testDirs as $inputDir => $outputDir) {
+ $in = _quote($inputDir . "/");
+ $rewritten = preg_replace("/$in/", $outputDir . "/", $out);
+ if ($rewritten != $out) {
+ $out = $rewritten;
+ break;
+ }
+ }
+
+ $out = preg_replace("/.less$/", ".css", $out);
+
+ return __DIR__ . "/" . $out;
+ }
+}
+
diff --git a/vendor/leafo/lessphp/tests/README.md b/vendor/leafo/lessphp/tests/README.md
new file mode 100644
index 00000000..85a75c05
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/README.md
@@ -0,0 +1,24 @@
+lessphp uses [phpunit](https://github.com/sebastianbergmann/phpunit/) for its tests
+
+* `InputTest.php` iterates through all the less files in `inputs/`, compiles
+ them, then compares the result with the respective file in `outputs/`.
+
+* `ApiTest.php` tests the behavior of lessphp's public API methods.
+
+* `ErrorHandlingTest.php` tests that lessphp throws appropriate errors when
+ given invalid LESS as input.
+
+From the root you can run `make` to run all the tests.
+
+## lessjs tests
+
+Tests found in `inputs_lessjs` are extracted directly from
+[less.js](https://github.com/less/less.js). The following license applies to
+those tests: https://github.com/less/less.js/blob/master/LICENSE
+
+## bootstrap.sh
+
+Clones twitter bootsrap, compiles it with lessc and lessphp, cleans up results
+with sort.php, and outputs diff. To run it, you need to have git and lessc
+installed.
+
diff --git a/vendor/leafo/lessphp/tests/bootstrap.sh b/vendor/leafo/lessphp/tests/bootstrap.sh
new file mode 100644
index 00000000..18a90e87
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/bootstrap.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+
+echo "This script clones Twitter Bootstrap, compiles it with lessc and lessphp,"
+echo "cleans up results with sort.php, and outputs diff. To run it, you need to"
+echo "have git and lessc installed."
+echo ""
+
+if [ -z "$input" ]; then
+ input="bootstrap/less/bootstrap.less"
+fi
+dest=$(basename "$input")
+dest="${dest%.*}"
+
+if [ -z "$@" ]; then
+ diff_tool="diff -b -u -t -B"
+else
+ diff_tool=$@
+fi
+
+mkdir -p tmp
+
+if [ ! -d 'bootstrap/' ]; then
+ echo ">> Cloning bootstrap to bootstrap/"
+ git clone https://github.com/twbs/bootstrap
+fi
+
+echo ">> lessc compilation ($input)"
+lessc "$input" "tmp/$dest.lessc.css"
+
+echo ">> lessphp compilation ($input)"
+../plessc "$input" "tmp/$dest.lessphp.css"
+echo ">> Cleanup and convert"
+
+php sort.php "tmp/$dest.lessc.css" > "tmp/$dest.lessc.clean.css"
+php sort.php "tmp/$dest.lessphp.css" > "tmp/$dest.lessphp.clean.css"
+
+echo ">> Doing diff"
+$diff_tool "tmp/$dest.lessc.clean.css" "tmp/$dest.lessphp.clean.css"
diff --git a/vendor/leafo/lessphp/tests/inputs/accessors.less.disable b/vendor/leafo/lessphp/tests/inputs/accessors.less.disable
new file mode 100644
index 00000000..2990faab
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/accessors.less.disable
@@ -0,0 +1,36 @@
+/* accessors */
+
+#defaults {
+ @width: 960px;
+ @color: black;
+ .something {
+ @space: 10px;
+ @hello {
+ color: green;
+ }
+ }
+}
+
+.article { color: #294366; }
+
+.comment {
+ width: #defaults[@width];
+ color: .article['color'];
+ padding: #defaults > .something[@space];
+}
+
+.wow {
+ height: .comment['width'];
+ background-color: .comment['color'];
+ color: #defaults > .something > @hello['color'];
+
+ padding: #defaults > non-existant['padding'];
+ margin: #defaults > .something['non-existant'];
+}
+
+.mix {
+ #defaults;
+ font-size: .something[@space];
+}
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs/arity.less b/vendor/leafo/lessphp/tests/inputs/arity.less
new file mode 100644
index 00000000..9998fd4a
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/arity.less
@@ -0,0 +1,77 @@
+
+// simple arity
+
+.hello(@a) {
+ hello: one;
+}
+
+.hello(@a, @b) {
+ hello: two;
+}
+
+.hello(@a, @b, @c) {
+ hello: three;
+}
+
+
+.world(@a, @b, @c) {
+ world: three;
+}
+
+.world(@a, @b) {
+ world: two;
+}
+
+.world(@a) {
+ world: one;
+}
+
+.one {
+ .hello(1);
+ .world(1);
+}
+
+.two {
+ .hello(1, 1);
+ .world(1, 1);
+}
+
+.three {
+ .hello(1, 1, 1);
+ .world(1, 1, 1);
+}
+
+
+// arity with default values
+
+.foo(@a, @b: cool) {
+ foo: two @b;
+}
+
+.foo(@a, @b: cool, @c: yeah) {
+ foo: three @b @c;
+}
+
+
+.baz(@a, @b, @c: yeah) {
+ baz: three @c;
+}
+
+.baz(@a, @b: cool) {
+ baz: two @b;
+}
+
+
+.multi-foo {
+ .foo(1);
+ .foo(1, 1);
+ .foo(1,1,1);
+}
+
+.multi-baz {
+ .baz(1);
+ .baz(1, 1);
+ .baz(1,1,1);
+}
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs/attributes.less b/vendor/leafo/lessphp/tests/inputs/attributes.less
new file mode 100644
index 00000000..7ede4fc4
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/attributes.less
@@ -0,0 +1,41 @@
+* { color: blue; }
+E { color: blue; }
+E[foo] { color: blue; }
+[foo] { color: blue; }
+[foo] .helloWorld { color: blue; }
+[foo].helloWorld { color: blue; }
+E[foo="barbar"] { color: blue; }
+E[foo~="hello#$@%@$#^"] { color: blue; }
+E[foo^="color: green;"] { color: blue; }
+E[foo$="239023"] { color: blue; }
+E[foo*="29302"] { color: blue; }
+E[foo|="239032"] { color: blue; }
+E:root { color: blue; }
+
+E:nth-child(odd) { color: blue; }
+E:nth-child(2n+1) { color: blue; }
+E:nth-child(5) { color: blue; }
+E:nth-last-child(-n+2) { color: blue; }
+E:nth-of-type(2n) { color: blue; }
+E:nth-last-of-type(n) { color: blue; }
+
+E:first-child { color: blue; }
+E:last-child { color: blue; }
+E:first-of-type { color: blue; }
+E:last-of-type { color: blue; }
+E:only-child { color: blue; }
+E:only-of-type { color: blue; }
+E:empty { color: blue; }
+
+E:lang(en) { color: blue; }
+E::first-line { color: blue; }
+E::before { color: blue; }
+
+E#id { color: blue; }
+E:not(:link) { color: blue; }
+
+E F { color: blue; }
+E > F { color: blue; }
+E + F { color: blue; }
+E ~ F { color: blue; }
+
diff --git a/vendor/leafo/lessphp/tests/inputs/builtins.less b/vendor/leafo/lessphp/tests/inputs/builtins.less
new file mode 100644
index 00000000..f9301e04
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/builtins.less
@@ -0,0 +1,96 @@
+// builtin
+
+@something: "hello world";
+@color: #112233;
+@color2: rgba(44,55,66, .6);
+
+body {
+ color: @something;
+
+ @num: 7 / 6;
+ num-basic: @num + 4;
+ num-floor: floor(@num) + 4px;
+ num-ceil: ceil(@num) + 4px;
+
+ @num2: 2 / 3;
+ num2: @num2;
+ num2-round: round(@num2);
+ num2-floor: floor(@num2);
+ num2-ceil: ceil(@num2);
+
+ round-lit: round(10px / 3);
+
+ rgba1: rgbahex(@color);
+ rgba2: rgbahex(@color2);
+ argb: argb(@color2);
+}
+
+
+format {
+ @r: 32;
+ format: %("rgb(%d, %d, %d)", @r, 128, 64);
+ format-string: %("hello %s", "world");
+ format-multiple: %("hello %s %d", "earth", 2);
+ format-url-encode: %('red is %A', #ff0000);
+ eformat: e(%("rgb(%d, %d, %d)", @r, 128, 64));
+}
+
+
+#functions {
+ str1: isstring("hello");
+ str2: isstring(one, two);
+
+ num1: isnumber(2323px);
+ num2: isnumber(2323);
+ num3: isnumber(4/5);
+ num4: isnumber("hello");
+
+ col1: iscolor(red);
+ col2: iscolor(hello);
+ col3: iscolor(rgba(0,0,0,0.3));
+ col4: iscolor(#fff);
+
+ key1: iskeyword(hello);
+ key2: iskeyword(3D);
+
+ px1: ispixel(10px);
+ px2: ispixel(10);
+
+ per1: ispercentage(10%);
+ per2: ispercentage(10);
+
+ em1: isem(10em);
+ em2: isem(10);
+
+ ex1: extract(1 2 3 4, 2);
+ ex2: extract(1 2, 1);
+ ex3: extract(1, 1);
+
+ @list: 1,2,3,4;
+
+ ex4: extract(@list, 2);
+
+ pow: pow(2,4);
+ pi: pi();
+ mod: mod(14,10);
+
+ tan: tan(1);
+ cos: cos(1);
+ sin: sin(1);
+
+ atan: atan(1);
+ acos: acos(1);
+ asin: asin(1);
+
+ sqrt: sqrt(8);
+}
+
+
+#unit {
+ @unit: "em";
+ unit-lit: unit(10px);
+ unit-arg: unit(10px, "s");
+ unit-arg2: unit(10px, @unit);
+ unit-math: unit(0.07407s) * 100%;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/colors.less b/vendor/leafo/lessphp/tests/inputs/colors.less
new file mode 100644
index 00000000..7fd47a3c
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/colors.less
@@ -0,0 +1,154 @@
+
+body {
+ color:rgb(red(#f00), red(#0F0), red(#00f));
+ color:rgb(red(#f00), green(#0F0), blue(#00f));
+ color:rgb(red(#0ff), green(#f0f), blue(#ff0));
+
+ color: hsl(34, 50%, 40%);
+ color: hsla(34, 50%, 40%, 0.3);
+
+ lighten1: lighten(#efefef, 10%);
+ lighten2: lighten(rgb(23, 53, 231), 22%);
+ lighten3: lighten(rgba(212, 103, 88, 0.5), 10%);
+
+ darken1: darken(#efefef, 10%);
+ darken2: darken(rgb(23, 53, 231), 22%);
+ darken3: darken(rgba(23, 53, 231, 0.5), 10%);
+
+ saturate1: saturate(#efefef, 10%);
+ saturate2: saturate(rgb(23, 53, 231), 22%);
+ saturate3: saturate(rgba(23, 53, 231, 0.5), 10%);
+
+ desaturate1: desaturate(#efefef, 10%);
+ desaturate2: desaturate(rgb(23, 53, 231), 22%);
+ desaturate3: desaturate(rgba(23, 53, 231, 0.5), 10%);
+
+ spin1: spin(#efefef, 12);
+ spin2: spin(rgb(23, 53, 231), 15);
+ spin3: spin(rgba(23, 53, 231, 0.5), 19);
+
+ spin2: spin(#efefef, -12);
+ spin3: spin(rgb(23, 53, 231), -15);
+ spin4: spin(rgba(23, 53, 231, 0.5), -19);
+
+ one1: fadein(#abcdef, 10%);
+ one2: fadeout(#abcdef, -10%);
+
+ two1: fadeout(#029f23, 10%);
+ two2: fadein(#029f23, -10%);
+
+
+ three1: fadein(rgba(1,2,3, 0.5), 10%);
+ three2: fadeout(rgba(1,2,3, 0.5), -10%);
+
+ four1: fadeout(rgba(1,2,3, 0), 10%);
+ four2: fadein(rgba(1,2,3, 0), -10%);
+
+ hue: hue(rgb(34,20,40));
+ sat: saturation(rgb(34,20,40));
+ lit: lightness(rgb(34,20,40));
+
+ @old: #34fa03;
+ @new: hsl(hue(@old), 45%, 90%);
+ what: @new;
+
+ zero1: saturate(#123456, -100%);
+ zero2: saturate(#123456, 100%);
+ zero3: saturate(#000000, 100%);
+ zero4: saturate(#ffffff, 100%);
+
+ zero5: lighten(#123456, -100%);
+ zero6: lighten(#123456, 100%);
+ zero7: lighten(#000000, 100%);
+ zero8: lighten(#ffffff, 100%);
+
+ zero9: spin(#123456, -100);
+ zeroa: spin(#123456, 100);
+ zerob: spin(#000000, 100);
+ zeroc: spin(#ffffff, 100);
+}
+
+
+alpha {
+ // g: alpha(red);
+ g1: alpha(rgba(0,0,0,0));
+ g2: alpha(rgb(155,55,0));
+}
+
+fade {
+ f1: fade(red, 50%);
+ f2: fade(#fff, 20%);
+ f3: fade(rgba(34,23,64,0.4), 50%);
+}
+
+@a: rgb(255,255,255);
+@b: rgb(0,0,0);
+
+.mix {
+ color1: mix(@a, @b, 50%);
+ color2: mix(@a, @b);
+ color3: mix(rgba(5,3,1,0.3), rgba(6,3,2, 0.8), 50%);
+}
+
+.contrast {
+ color1: contrast(#000, red, blue);
+ color2: contrast(#fff, red, blue);
+}
+
+.percent {
+ per: percentage(0.5);
+}
+
+// color keywords
+
+.colorz {
+ color1: whitesmoke - 10;
+ color2: spin(red, 34);
+ color3: spin(blah);
+}
+
+
+
+// purposfuly whacky to match less.js
+
+@color: #fcf8e3;
+
+body {
+ start: @color;
+ spin: spin(@color, -10); // #fcf4e3
+ chained: darken(spin(@color, -10), 3%); // gives #fbeed5, should be #fbefd5
+ direct: darken(#fcf4e3, 3%); // #fbefd5
+}
+
+// spin around
+pre {
+ @errorBackground: #f2dede;
+ spin: spin(@errorBackground, -10);
+}
+
+dd {
+ @white: #fff;
+ background-color: mix(@white, darken(@white, 10%), 60%);
+}
+
+// math
+
+.ops {
+ c1: red * 0.3;
+ c2: green / 2;
+ c3: purple % 7;
+ c4: 4 * salmon;
+ c5: 1 + salmon;
+
+ c6: 132 / red;
+ c7: 132 - red;
+ c8: 132- red;
+}
+
+.transparent {
+ r: red(transparent);
+ g: green(transparent);
+ b: blue(transparent);
+ a: alpha(transparent);
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/compile_on_mixin.less b/vendor/leafo/lessphp/tests/inputs/compile_on_mixin.less
new file mode 100644
index 00000000..79d628f4
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/compile_on_mixin.less
@@ -0,0 +1,39 @@
+
+@mixin {
+ height: 22px;
+ ul {
+ height: 20px;
+ li {
+ @color: red;
+ height: 10px;
+ div span, link {
+ margin: 10px;
+ color: @color;
+ }
+ }
+
+ div, p {
+ border: 1px;
+ &.hello {
+ color: green;
+ }
+
+ :what {
+ color: blue;
+ }
+ }
+
+
+ a {
+ b {
+ color: blue;
+ }
+ }
+ }
+}
+
+
+
+body {
+ @mixin;
+}
diff --git a/vendor/leafo/lessphp/tests/inputs/data-uri.less b/vendor/leafo/lessphp/tests/inputs/data-uri.less
new file mode 100644
index 00000000..0cfa941a
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/data-uri.less
@@ -0,0 +1,7 @@
+.small {
+ background: data-uri("../hi.less");
+}
+
+.large {
+ background: data-uri('../../../lessc.inc.php');
+}
diff --git a/vendor/leafo/lessphp/tests/inputs/directives.less b/vendor/leafo/lessphp/tests/inputs/directives.less
new file mode 100644
index 00000000..1b3a9b58
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/directives.less
@@ -0,0 +1,28 @@
+
+@hello: "utf-8";
+@charset @hello;
+
+@-moz-document url-prefix(){
+ div {
+ color: red;
+ }
+}
+
+@page :left { margin-left: 4cm; }
+@page :right { margin-left: 3cm; }
+@page { margin: 2cm }
+
+@-ms-viewport {
+ width: device-width;
+}
+@-moz-viewport {
+ width: device-width;
+}
+@-o-viewport {
+ width: device-width;
+}
+@viewport {
+ width: device-width;
+}
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs/escape.less b/vendor/leafo/lessphp/tests/inputs/escape.less
new file mode 100644
index 00000000..5c15e786
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/escape.less
@@ -0,0 +1,18 @@
+
+body {
+ @hello: "world";
+ e1: e("this is simple");
+ e2: e("this is simple", "cool lad");
+ e3: e(1232);
+ e4: e(@hello);
+ e5: e("one" + 'more');
+
+ t1: ~"eating rice";
+ t2: ~"string cheese";
+ t3: a b c ~"string me" d e f;
+ t4: ~"string @{hello}";
+}
+
+.class {
+ filter: ~"progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png')";
+}
diff --git a/vendor/leafo/lessphp/tests/inputs/font_family.less b/vendor/leafo/lessphp/tests/inputs/font_family.less
new file mode 100644
index 00000000..d1dfb72d
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/font_family.less
@@ -0,0 +1,28 @@
+
+@font-directory: 'fonts/';
+@some-family: Gentium;
+
+@font-face: maroon; // won't collide with @font-face { }
+
+@font-face {
+ font-family: Graublau Sans Web;
+ src: url(@{font-directory}GraublauWeb.otf) format("opentype");
+}
+
+@font-face {
+ font-family: @some-family;
+ src: url('@{font-directory}Gentium.ttf');
+}
+
+@font-face {
+ font-family: @some-family;
+ src: url("@{font-directory}GentiumItalic.ttf");
+ font-style: italic;
+}
+
+h2 {
+ font-family: @some-family;
+ crazy: @font-face;
+}
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs/guards.less b/vendor/leafo/lessphp/tests/inputs/guards.less
new file mode 100644
index 00000000..fc4e344e
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/guards.less
@@ -0,0 +1,74 @@
+
+.simple(@hi) when (@hi) {
+ simple: yellow;
+}
+
+
+.something(@hi) when (@hi = cool) {
+ something: red;
+}
+
+.another(@x) when (@x > 10) {
+ another: green;
+}
+
+
+.flipped(@x) when (@x =< 10) {
+ flipped: teal;
+}
+
+.yeah(@arg) when (isnumber(@arg)) {
+ yeah-number: purple @arg;
+}
+
+
+.yeah(@arg) when (ispixel(@arg)) {
+ yeah-pixel: silver;
+}
+
+
+.hello(@arg) when not (@arg) {
+ hello: orange;
+}
+
+dd {
+ .simple(true);
+}
+
+b {
+ .something(cool);
+ .something(birthday);
+}
+
+img {
+ .another(12);
+ .flipped(2);
+}
+
+body {
+ .yeah(232px);
+ .yeah(232);
+}
+
+.something(@x) when (@x) and (@y), not (@x = what) {
+ something-complex: blue @x;
+}
+
+div {
+ @y: true;
+ .something(true);
+
+}
+
+.coloras(@g) when (iscolor(@g)) {
+ color: true @g;
+}
+
+link {
+ .coloras(red);
+ .coloras(#fff);
+ .coloras(#fffddd);
+ .coloras(rgb(0,0,0));
+ .coloras(rgba(0,0,0, .34));
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/hacks.less b/vendor/leafo/lessphp/tests/inputs/hacks.less
new file mode 100644
index 00000000..e69b7bf9
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/hacks.less
@@ -0,0 +1,6 @@
+// css hacks
+
+:root .alert-message, :root .btn {
+ border-radius: 0 \0;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/hi.less b/vendor/leafo/lessphp/tests/inputs/hi.less
new file mode 100644
index 00000000..9a3d8198
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/hi.less
@@ -0,0 +1,5 @@
+
+div:before {
+ content: "hi!";
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/ie.less b/vendor/leafo/lessphp/tests/inputs/ie.less
new file mode 100644
index 00000000..37a5f1f6
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/ie.less
@@ -0,0 +1,12 @@
+
+foo {
+ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000000);
+ filter:progid:DXImageTransform.Microsoft.gradient(GradientType=1, startColorstr=#c0ff3300, endColorstr=#ff000001);
+}
+
+
+foo {
+ filter: alpha(opacity=20);
+ filter: alpha(opacity=20, enabled=true);
+ filter: blaznicate(foo=bar, baz=bang bip, bart=#fa4600);
+}
diff --git a/vendor/leafo/lessphp/tests/inputs/import.less b/vendor/leafo/lessphp/tests/inputs/import.less
new file mode 100644
index 00000000..a5005674
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/import.less
@@ -0,0 +1,56 @@
+
+@import 'file1.less'; // file found and imported
+
+@import "not-found";
+
+@import "something.css" media;
+@import url("something.css") media;
+@import url(something.css) media, screen, print;
+
+@color: maroon;
+
+@import url(file2); // found and imported
+
+body {
+ line-height: 10em;
+ @colors;
+}
+
+div {
+ @color: fuchsia;
+ @import "file2";
+}
+
+
+.mixin-import() {
+ @import "file3";
+}
+
+.one {
+ .mixin-import();
+ color: blue;
+}
+
+.two {
+ .mixin-import();
+}
+
+
+#merge-import-mixins {
+ @import "a";
+ @import "b";
+ div { .just-a-class; }
+}
+
+
+@import "inner/file1";
+
+
+// test bubbling variables up from imports, while preserving order
+
+pre {
+ color: @someValue;
+}
+
+@import "file3";
+
diff --git a/vendor/leafo/lessphp/tests/inputs/interpolation.less b/vendor/leafo/lessphp/tests/inputs/interpolation.less
new file mode 100644
index 00000000..8aad0004
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/interpolation.less
@@ -0,0 +1,47 @@
+
+@cool-hello: "yes";
+@cool-yes: "okay";
+@var: "hello";
+
+div {
+ interp1: ~"@{cool-hello}";
+ interp2: ~"@{cool-@{var}}";
+ interp3: ~"@{cool-@{cool-@{var}}}";
+}
+
+// interpolation in selectors
+
+@hello: 10;
+@world: "yeah";
+
+@{hello}@{world} {
+ color: blue;
+}
+
+@{hello} {
+ color: blue;
+}
+
+hello world @{hello} {
+ color: red;
+}
+
+#@{world} {
+ color: "hello @{hello}";
+}
+
+
+@num: 3;
+
+[prop],
+[prop="value@{num}"],
+[prop*="val@{num}"],
+[|prop~="val@{num}"],
+[*|prop$="val@{num}"],
+[ns|prop^="val@{num}"],
+[@{num}^="val@{num}"],
+[@{num}=@{num}],
+[@{num}] {
+ attributes: yes;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/keyframes.less b/vendor/leafo/lessphp/tests/inputs/keyframes.less
new file mode 100644
index 00000000..e65a38b9
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/keyframes.less
@@ -0,0 +1,52 @@
+@keyframes 'bounce' {
+ from {
+ top: 100px;
+ animation-timing-function: ease-out;
+ }
+
+ 25% {
+ top: 50px;
+ animation-timing-function: ease-in;
+ }
+
+ 50% {
+ top: 100px;
+ animation-timing-function: ease-out;
+ }
+
+ 75% {
+ top: 75px;
+ animation-timing-function: ease-in;
+ }
+
+ to {
+ top: 100px;
+ }
+}
+
+@-webkit-keyframes flowouttoleft {
+ 0% { -webkit-transform: translateX(0) scale(1); }
+ 60%, 70% { -webkit-transform: translateX(0) scale(.7); }
+ 100% { -webkit-transform: translateX(-100%) scale(.7); }
+}
+
+div {
+ animation-name: 'diagonal-slide';
+ animation-duration: 5s;
+ animation-iteration-count: 10;
+}
+
+@keyframes 'diagonal-slide' {
+
+ from {
+ left: 0;
+ top: 0;
+ }
+
+ to {
+ left: 100px;
+ top: 100px;
+ }
+
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/math.less b/vendor/leafo/lessphp/tests/inputs/math.less
new file mode 100644
index 00000000..db59d356
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/math.less
@@ -0,0 +1,122 @@
+
+.unary {
+ // all operators are parsable as unary operators, anything
+ // but - throws an error right now though,
+
+ // this gives two numbers
+ sub: 10 -5;
+ // add: 10 +5; // error
+ // div: 10 /5; // error
+ // mul: 10 *5; // error
+}
+
+.spaces {
+ // we can make the parser do math by leaving out the
+ // space after the first value, or putting spaces on both sides
+
+ sub1: 10-5;
+ sub2: 10 - 5;
+
+ add1: 10+5;
+ add2: 10 + 5;
+
+ // div: 10/5; // this wont work, read on
+ div: 10 / 5;
+
+ mul1: 10*5;
+ mul2: 10 * 5;
+}
+
+// these properties have divison not in parenthases
+.supress-division {
+ border-radius: 10px / 10px;
+ border-radius: 10px/12px;
+ border-radius: hello (10px/10px) world;
+ @x: 10px;
+ font: @x/30 sans-serif;
+ font: 10px / 20px sans-serif;
+ font: 10px/22px sans-serif;
+ border-radius:0 15px 15px 15px / 0 50% 50% 50%;
+}
+
+
+.parens {
+ // if you are unsure, then just wrap the expression in parentheses and it will
+ // always evaluate.
+
+ // notice we no longer have unary operators, and these will evaluate
+ sub: (10 -5);
+ add: (10 +5);
+ div1: (10 /5);
+ div2: (10/5); // no longer interpreted as the shorthand
+ mul: (10 *5);
+}
+
+.keyword-names {
+ // watch out when doing math with keywords, - is a valid keyword character
+ @a: 100;
+ @b: 25;
+ @a-: "hello";
+ height: @a-@b; // here we get "hello" 25, not 75
+}
+
+
+.negation {
+ neg1: -(1px);
+ neg2: 0-(1px);
+
+ @something: 10;
+ neg3: -@something;
+}
+
+
+// and now here are the tests
+
+.test {
+ single1: (5);
+ single2: 5+(5);
+ single3: (5)+((5));
+
+ parens: (5 +(5)) -2;
+ // parens: ((5 +(5)) -2); // FAILS - fixme
+
+ math1: (5 + 5)*(2 / 1);
+ math2: (5+5)*(2/1);
+
+ complex1: 2 * (4 * (2 + (1 + 6))) - 1;
+ complex2: ((2+3)*(2+3) / (9-4)) + 1;
+ complex3: (2px + 4px) 1em 2px 2;
+
+ @var: (2 * 2);
+ var1: (2 * @var) 4 4 (@var * 1px);
+ var2: (@var * @var) * 6;
+ var3: 4 * (5 + 5) / 2 - (@var * 2);
+
+ complex4: (7 * 7) + (8 * 8);
+}
+
+.percents {
+ p1: 100 * 10%;
+ p2: 10% * 100;
+ p3: 10% * 10%;
+
+ p4: 100px * 10%; // lessjs makes this px
+ p5: 10% * 100px; // lessjs makes this %
+
+ p6: 20% + 10%;
+ p7: 20% - 10%;
+
+ p8: 20% / 10%;
+}
+
+.misc {
+ x: 10px * 4em;
+ y: 10 * 4em;
+}
+
+
+.cond {
+ c1: 10 < 10;
+ c2: 10 >= 10;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/media.less b/vendor/leafo/lessphp/tests/inputs/media.less
new file mode 100644
index 00000000..8c16a3cf
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/media.less
@@ -0,0 +1,68 @@
+@media screen, 3D {
+ P { color: green; }
+}
+@media print {
+ body { font-size: 10pt }
+}
+@media screen {
+ body { font-size: 13px }
+}
+@media screen, print {
+ body { line-height: 1.2 }
+}
+
+@media all and (min-width: 0px) {
+ body { line-height: 1.2 }
+}
+
+@media all and (min-width: 0) {
+ body { line-height: 1.2 }
+}
+
+@media
+ screen and (min-width: 102.5em) and (max-width: 117.9375em),
+ screen and (min-width: 150em) {
+ body { color: blue }
+}
+
+
+@media screen and (min-height: 100px + 10px) {
+ body { color: red; }
+}
+
+@cool: 100px;
+
+@media screen and (height: @cool) and (width: @cool + 10), (size: @cool + 20) {
+ body { color: red; }
+}
+
+
+// media bubbling
+
+@media test {
+ div {
+ height: 20px;
+ @media (hello) {
+ color: red;
+
+ pre {
+ color: orange;
+ }
+ }
+ }
+}
+
+// should not cross boundary
+@media yeah {
+ @page {
+ @media cool {
+ color: red;
+ }
+ }
+}
+
+// variable in query
+@mobile: ~"(max-width: 599px)";
+@media @mobile {
+ .helloworld { color: blue }
+}
diff --git a/vendor/leafo/lessphp/tests/inputs/misc.less b/vendor/leafo/lessphp/tests/inputs/misc.less
new file mode 100644
index 00000000..eb690b9a
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/misc.less
@@ -0,0 +1,100 @@
+
+@color: #fff;
+@base_path: "/assets/images/";
+@images: @base_path + "test/";
+.topbar { background: url(@{images}topbar.png); }
+.hello { test: empty-function(@images, 40%, to(@color)); }
+
+.css3 {
+ background-image: -webkit-gradient(linear, 0% 0%, 0% 90%,
+ from(#E9A000), to(#A37000));
+}
+
+
+/**
+
+Here is a block comment
+
+**/
+
+
+// this is a comment
+
+.test, /*hello*/.world {
+ border: 1px solid red; // world
+ /* another property */
+ color: url(http://mage-page.com);
+ string: "hello /* this is not a comment */";
+ world: "// neither is this";
+ string: 'hello /* this is not a comment */' /*what if this is a comment */;
+ world: '// neither is this' // hell world;
+ ;
+ what-/*something?*/ever: 100px;
+ background: url(/*no comment here*/);
+}
+
+
+.urls {
+ @var: "http://google.com";
+ background1: url(@var);
+ background2: url(@{var});
+ background3: url("@{var}");
+}
+
+.mix(@arg) { color: @arg; }
+@aaa: aaa;
+@bbb: bbb;
+// make sure the opening selector isn't too greedy
+.cool {.mix("@{aaa}, @{bbb}")}
+.cool();
+
+
+
+// merging of mixins
+.span-17 { float: left; }
+.span-17 { width: 660px; }
+
+.x {.span-17;}
+
+.hi {
+ pre {
+ color: red;
+ }
+}
+
+.hi {
+ pre {
+ color: blue;
+ }
+}
+
+.rad {
+ .hi;
+}
+
+
+hello {
+ numbers: 1.0 0.1 .1 1.;
+ numbers: 1.0s 0.1s .1s 1.s;
+ numbers: -1.0s -0.1s -.1s -1.s;
+ numbers: -1.0 -0.1 -.1 -1.;
+}
+
+
+#string {
+ hello: 'what\'s going on here';
+ hello: 'blah blag @{ blah blah';
+
+ join: 3434 + "hello";
+ join: 3434 + hello;
+}
+
+
+.duplicates {
+ hello: world;
+ hello: "world";
+ hello: world;
+ hello: "what";
+ hello: world;
+ hello: "world";
+}
diff --git a/vendor/leafo/lessphp/tests/inputs/mixin_functions.less b/vendor/leafo/lessphp/tests/inputs/mixin_functions.less
new file mode 100644
index 00000000..2d858ad6
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/mixin_functions.less
@@ -0,0 +1,33 @@
+
+@outer: 10px;
+@class(@var:22px, @car: 400px + @outer) {
+ margin: @var;
+ height: @car;
+}
+
+@group {
+ @f(@color) {
+ color: @color;
+ }
+ .cool {
+ border-bottom: 1px solid green;
+ }
+}
+
+.class(@width:200px) {
+ padding: @width;
+}
+
+body {
+ .class(2.0em);
+ @group > @f(red);
+ @class(10px, 10px + 2);
+ @group > .cool;
+}
+
+
+@lots(@a: 10px, @b: 20px, @c: 30px, @d: 40px, @e: 4px, @f:3px, @g:2px, @h: 1px) {
+ padding: @a @b @c @d;
+ margin: @e @f @g @h;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/mixin_merging.less.disable b/vendor/leafo/lessphp/tests/inputs/mixin_merging.less.disable
new file mode 100644
index 00000000..86b3e0cb
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/mixin_merging.less.disable
@@ -0,0 +1,100 @@
+
+@tester {
+ p, div { height: 10px; }
+}
+
+#test1 {
+ div { color: red; }
+ @tester;
+}
+
+
+@cool {
+ a,b,i { width: 1px; }
+}
+
+#test2 {
+ b { color: red; }
+ @cool;
+}
+
+#test3 {
+ @cool;
+ b { color: red; }
+}
+
+@cooler {
+ a { margin: 1px; }
+}
+
+#test4 {
+ a, div, html { color: blue; }
+ @cooler;
+}
+
+@hi {
+ img, strong { float: right; }
+}
+
+#test5 {
+ img, strong { padding: 2px; }
+ @hi;
+}
+
+@nested {
+ div, span {
+ a {
+ color: red;
+ }
+ }
+}
+
+#test6 {
+ div, span {
+ a {
+ line-height: 10px;
+ }
+ }
+ @nested;
+}
+
+@broken-nesting {
+ div, span {
+ strong, b {
+ color: red;
+ }
+ }
+
+}
+
+#test7 {
+ div {
+ strong {
+ margin: 1px;
+ }
+ }
+ @broken-nesting;
+}
+
+
+@another-nest {
+ a,b {
+ i {
+ color: red;
+ }
+
+ s {
+ color: blue;
+ }
+ }
+}
+
+#test8 {
+ a, b {
+ i,s {
+ background: red;
+ }
+ }
+ @another-nest;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/mixins.less b/vendor/leafo/lessphp/tests/inputs/mixins.less
new file mode 100644
index 00000000..768e6384
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/mixins.less
@@ -0,0 +1,197 @@
+
+@rounded-corners {
+ border-radius: 10px;
+}
+
+.bold {
+ @font-size: 20px;
+ font-size: @font-size;
+ font-weight: bold;
+}
+
+body #window {
+ @rounded-corners;
+ .bold;
+ line-height: @font-size * 1.5;
+}
+
+#bundle {
+ .button {
+ display: block;
+ border: 1px solid black;
+ background-color: grey;
+ &:hover { background-color: white }
+ }
+}
+#header a {
+ color: orange;
+ #bundle > .button; // mixin the button class
+}
+
+div {
+ @abstract {
+ hello: world;
+ b {
+ color: blue;
+ }
+ }
+
+ @abstract > b;
+ @abstract;
+}
+
+@poop {
+ big: baby;
+}
+
+body {
+ div;
+}
+
+// not using > to list mixins
+
+.hello {
+ .world {
+ color: blue;
+ }
+}
+
+.foobar {
+ .hello .world;
+}
+
+
+// arguments
+
+.spam(@something: 100, @dad: land) {
+ @wow: 23434;
+ foo: @arguments;
+ bar: @arguments;
+}
+
+.eggs {
+ .spam(1px, 2px);
+ .spam();
+}
+
+.first(@one, @two, @three, @four: cool) {
+ cool: @arguments;
+}
+
+#hello {
+ .first(one, two, three);
+}
+
+#hello-important {
+ .first(one, two, three) !important;
+}
+
+.rad(@name) {
+ cool: @arguments;
+}
+
+#world {
+ @hello: "world";
+ .rad("@{hello}");
+}
+
+.second(@x, @y:skip, @z: me) {
+ things: @arguments;
+}
+
+#another {
+ .second(red, blue, green);
+ .second(red blue green);
+}
+
+
+.another(@x, @y:skip, @z:me) {
+ .cool {
+ color: @arguments;
+ }
+}
+
+#day {
+ .another(one,two, three);
+ .another(one two three);
+}
+
+
+.to-be-important() {
+ color: red;
+ @color: red;
+ height: 20px;
+
+ pre {
+ color: @color;
+ }
+}
+
+.mix-suffix {
+ .to-be-important() !important;
+}
+
+
+
+
+#search-all {
+ .red() {
+ color:#f00 !important;
+ }
+}
+
+#search-all {
+ .green() {
+ color: #0f0 !important;
+ }
+}
+
+.search-test {
+ #search-all > .red();
+ #search-all > .green();
+}
+
+
+// mixin self without infinite loop
+.cowboy() {
+ color: blue;
+}
+
+.cowboy {
+ .cowboy;
+}
+
+
+// semicolon
+
+.semi1(@color: red, blue, green;) {
+ color: @color;
+}
+
+.semi2(@color: red, blue, green; dad) {
+ color: @color;
+}
+
+.semi3(hello; world; piss) {
+ hello: world;
+}
+
+
+
+// self referencing skipping
+
+.nav-divider(@color: red){
+ padding: 10px;
+}
+
+.nav {
+ .nav-divider {
+ .nav-divider();
+ }
+}
+
+.nav-divider {
+ .nav-divider();
+}
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs/nested.less b/vendor/leafo/lessphp/tests/inputs/nested.less
new file mode 100644
index 00000000..0b62ea19
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/nested.less
@@ -0,0 +1,60 @@
+#header {
+ color: black;
+
+ .navigation {
+ font-size: 12px;
+ .border {
+ .outside {
+ color: blue;
+ }
+ }
+ }
+ .logo {
+ width: 300px;
+ &:hover { text-decoration: none }
+ }
+}
+
+a { b { ul { li { color: green; } } } }
+
+this { will { not { show { } } } }
+
+.cool {
+ div & { color: green; }
+ p & span { color: yellow; }
+}
+
+another {
+ .cool;
+}
+
+b {
+ & .something {
+ color: blue;
+ }
+
+ &.something {
+ color: blue;
+ }
+}
+
+.foo {
+ .bar, .baz {
+ & .qux {
+ display: block;
+ }
+ .qux & {
+ display: inline;
+ }
+ .qux & .biz {
+ display: none;
+ }
+ }
+}
+
+b {
+ hello [x="&yeah"] {
+ color: red;
+ }
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/pattern_matching.less b/vendor/leafo/lessphp/tests/inputs/pattern_matching.less
new file mode 100644
index 00000000..c2a0da91
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/pattern_matching.less
@@ -0,0 +1,157 @@
+
+.demo (light, @color) {
+ color: lighten(@color, 10%);
+}
+.demo (@_, @color) {
+ display: block;
+}
+
+@switch: light;
+
+.class {
+ .demo(@switch, #888);
+}
+
+// by arity
+
+.mixin () {
+ zero: 0;
+}
+.mixin (@a: 1px) {
+ one: 1;
+}
+.mixin (@a) {
+ one-req: 1;
+}
+.mixin (@a: 1px, @b: 2px) {
+ two: 2;
+}
+
+.mixin (@a, @b, @c) {
+ three-req: 3;
+}
+
+.mixin (@a: 1px, @b: 2px, @c: 3px) {
+ three: 3;
+}
+
+.zero {
+ .mixin();
+}
+
+.one {
+ .mixin(1);
+}
+
+.two {
+ .mixin(1, 2);
+}
+
+.three {
+ .mixin(1, 2, 3);
+}
+
+//
+
+.mixout ('left') {
+ left: 1;
+}
+
+.mixout ('right') {
+ right: 1;
+}
+
+.left {
+ .mixout('left');
+}
+.right {
+ .mixout('right');
+}
+
+//
+
+.border (@side, @width) {
+ color: black;
+ .border-side(@side, @width);
+}
+.border-side (left, @w) {
+ border-left: @w;
+}
+.border-side (right, @w) {
+ border-right: @w;
+}
+
+.border-right {
+ .border(right, 4px);
+}
+.border-left {
+ .border(left, 4px);
+}
+
+//
+
+
+.border-radius (@r) {
+ both: @r * 10;
+}
+.border-radius (@r, left) {
+ left: @r;
+}
+.border-radius (@r, right) {
+ right: @r;
+}
+
+.only-right {
+ .border-radius(33, right);
+}
+.only-left {
+ .border-radius(33, left);
+}
+.left-right {
+ .border-radius(33);
+}
+
+.hola(hello, @hello...) {
+ color: blue;
+}
+
+#hola {
+ .hola(hello, world);
+}
+
+.resty(@hello, @world, @the_rest...) {
+ padding: @hello @world;
+ rest: @the_rest;
+}
+
+.defaults(@aa, @bb:e343, @cc: "heah", ...) {
+ height: @aa;
+}
+
+#defaults_1 {
+ .defaults(one);
+ .defaults(two, one);
+ .defaults(three, two, one);
+ .defaults(four, three, two, one);
+}
+
+
+.thing() { color: green; }
+.thing(...) { color: blue; }
+.thing { color: red; }
+
+#aa {
+ .thing();
+}
+
+#bb {
+ .thing;
+}
+
+
+#cc {
+ .thing(1);
+}
+
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs/scopes.less b/vendor/leafo/lessphp/tests/inputs/scopes.less
new file mode 100644
index 00000000..42e46969
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/scopes.less
@@ -0,0 +1,40 @@
+
+
+@a: 10;
+@some {
+ @b: @a + 10;
+ div {
+ @c: @b + 10;
+ other {
+ @d: @c + 10;
+ world {
+ @e: @d + 10;
+ height: @e;
+ }
+ }
+ }
+}
+
+
+body {
+ @some;
+}
+
+@some;
+
+.test(@x: 10) {
+ height: @x;
+ .test(@y: 11) {
+ height: @y;
+ .test(@z: 12) {
+ height: @z;
+ }
+ .test;
+ }
+ .test;
+}
+
+pre {
+ .test;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/selector_expressions.less b/vendor/leafo/lessphp/tests/inputs/selector_expressions.less
new file mode 100644
index 00000000..b8aa2214
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/selector_expressions.less
@@ -0,0 +1,29 @@
+
+@color: blue;
+
+something @{color}, world {
+ color: blue;
+}
+
+.div {
+ @color: red;
+ (3434) {
+ height: 100px;
+ }
+
+ cool @{color} {
+ height: 4000px;
+ }
+}
+
+.heck(@a) { color: @a+10 }
+
+.spanX (@index) when (@index > 0) {
+ .span@{index} { .heck(@index) }
+ .spanX(@index - 1);
+}
+.spanX (0) {}
+
+.spanX (5);
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs/site_demos.less b/vendor/leafo/lessphp/tests/inputs/site_demos.less
new file mode 100644
index 00000000..08d7f1e5
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/site_demos.less
@@ -0,0 +1,120 @@
+// these are the demos from the lessphp homepage
+
+default {
+ @base: 24px;
+ @border-color: #B2B;
+
+ .underline { border-bottom: 1px solid green }
+
+ #header {
+ color: black;
+ border: 1px solid @border-color + #222222;
+
+ .navigation {
+ font-size: @base / 2;
+ a {
+ .underline;
+ }
+ }
+ .logo {
+ width: 300px;
+ &:hover { text-decoration: none }
+ }
+ }
+}
+
+variables {
+ @a: 2;
+ @x: @a * @a;
+ @y: @x + 1;
+ @z: @x * 2 + @y;
+
+ @nice-blue: #5B83AD;
+ @light-blue: @nice-blue + #111;
+
+ @b: @a * 10;
+ @c: #888;
+ @fonts: "Trebuchet MS", Verdana, sans-serif;
+
+ .variables {
+ width: @z + 1cm; // 14cm
+ height: @b + @x + 0px; // 24px
+ color: @c;
+ background: @light-blue;
+ font-family: @fonts;
+ }
+}
+
+mixins {
+ .bordered {
+ border-top: dotted 1px black;
+ border-bottom: solid 2px black;
+ }
+ #menu a {
+ color: #111;
+ .bordered;
+ }
+
+ .post a {
+ color: red;
+ .bordered;
+ }
+}
+
+nested-rules {
+ #header {
+ color: black;
+
+ .navigation {
+ font-size: 12px;
+ }
+ .logo {
+ width: 300px;
+ &:hover { text-decoration: none }
+ }
+ }
+}
+
+namespaces {
+ #bundle {
+ .button {
+ display: block;
+ border: 1px solid black;
+ background-color: grey;
+ &:hover { background-color: white }
+ }
+ }
+ #header a {
+ color: orange;
+ #bundle > .button; // mixin the button class
+ }
+}
+
+mixin-functions {
+ @outer: 10px;
+ @class(@var:22px, @car: 400px + @outer) {
+ margin: @var;
+ height: @car;
+ }
+
+ @group {
+ @f(@color) {
+ color: @color;
+ }
+ .cool {
+ border-bottom: 1px solid green;
+ }
+ }
+
+ .class(@width:200px) {
+ padding: @width;
+ }
+
+ body {
+ .class(2.0em);
+ @group > @f(red);
+ @class(10px, 10px + 2);
+ @group > .cool;
+ }
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/test-imports/a.less b/vendor/leafo/lessphp/tests/inputs/test-imports/a.less
new file mode 100644
index 00000000..a00464db
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/test-imports/a.less
@@ -0,0 +1,6 @@
+.just-a-class { background: red; }
+
+.some-mixin() {
+ height: 200px;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/test-imports/b.less b/vendor/leafo/lessphp/tests/inputs/test-imports/b.less
new file mode 100644
index 00000000..599ed3a4
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/test-imports/b.less
@@ -0,0 +1,12 @@
+.just-a-class { background: blue; }
+
+.hello {
+ .some-mixin();
+}
+
+
+@media cool {
+ color: red;
+ .some-mixin();
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/test-imports/file1.less b/vendor/leafo/lessphp/tests/inputs/test-imports/file1.less
new file mode 100644
index 00000000..658de0c5
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/test-imports/file1.less
@@ -0,0 +1,16 @@
+
+
+/**
+ * This is a test import file
+ */
+
+@colors {
+ div.bright {
+ color: red;
+ }
+
+ div.sad {
+ color: blue;
+ }
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/test-imports/file2.less b/vendor/leafo/lessphp/tests/inputs/test-imports/file2.less
new file mode 100644
index 00000000..2cae8dce
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/test-imports/file2.less
@@ -0,0 +1,6 @@
+
+b {
+ color: @color;
+ padding: 16px;
+}
+
diff --git a/vendor/leafo/lessphp/tests/inputs/test-imports/file3.less b/vendor/leafo/lessphp/tests/inputs/test-imports/file3.less
new file mode 100644
index 00000000..28b643ea
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/test-imports/file3.less
@@ -0,0 +1,7 @@
+
+h2 {
+ background: url("../images/logo.png") no-repeat;
+}
+
+@someValue: hello-from-file-3;
+
diff --git a/vendor/leafo/lessphp/tests/inputs/test-imports/inner/file1.less b/vendor/leafo/lessphp/tests/inputs/test-imports/inner/file1.less
new file mode 100644
index 00000000..df654a7e
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/test-imports/inner/file1.less
@@ -0,0 +1,6 @@
+
+.inner {
+ content: "inner/file1.less"
+}
+
+@import "file2"; // should get the one in inner
diff --git a/vendor/leafo/lessphp/tests/inputs/test-imports/inner/file2.less b/vendor/leafo/lessphp/tests/inputs/test-imports/inner/file2.less
new file mode 100644
index 00000000..f40d3c67
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/test-imports/inner/file2.less
@@ -0,0 +1,4 @@
+
+.inner {
+ content: "inner/file2.less"
+}
diff --git a/vendor/leafo/lessphp/tests/inputs/variables.less b/vendor/leafo/lessphp/tests/inputs/variables.less
new file mode 100644
index 00000000..6e18eb80
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs/variables.less
@@ -0,0 +1,44 @@
+@a: 2;
+@x: @a * @a;
+@y: @x + 1;
+@z: @y + @x * 2;
+@m: @z % @y;
+
+@nice-blue: #5B83AD;
+@light-blue: @nice-blue + #111;
+
+@rgb-color: rgb(20%, 15%, 80%);
+@rgba-color: rgba(23,68,149,0.5);
+
+@b: @a * 10px;
+@c: #888;
+@fonts: "Trebuchet MS", Verdana, sans-serif;
+
+.variables {
+ width: @z + 1cm; // 14cm
+ height: @b + @x + 0px; // 24px
+ margin-top: -@b; // -20px
+ margin-bottom: 10 - -@b; // 30px
+ @d: @c + #001;
+ color: @d;
+ background: @light-blue;
+ font-family: @fonts;
+ margin: @m + 0px; // 3px
+ font: 10px/12px serif;
+ font: 120%/120% serif;
+}
+
+.external {
+ color: @c;
+ border: 1px solid @rgb-color;
+ background: @rgba-color;
+}
+
+@hello: 44px;
+@something: "hello";
+@cool: something;
+
+outer1: @@something;
+outer2: @@@cool;
+
+
diff --git a/vendor/leafo/lessphp/tests/inputs_lessjs/mixins-args.less b/vendor/leafo/lessphp/tests/inputs_lessjs/mixins-args.less
new file mode 100644
index 00000000..c3965924
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs_lessjs/mixins-args.less
@@ -0,0 +1,205 @@
+.mixin (@a: 1px, @b: 50%) {
+ width: (@a * 5);
+ height: (@b - 1%);
+}
+
+.mixina (@style, @width, @color: black) {
+ border: @width @style @color;
+}
+
+.mixiny
+(@a: 0, @b: 0) {
+ margin: @a;
+ padding: @b;
+}
+
+.hidden() {
+ color: transparent; // asd
+}
+
+#hidden {
+ .hidden;
+}
+
+#hidden1 {
+ .hidden();
+}
+
+.two-args {
+ color: blue;
+ .mixin(2px, 100%);
+ .mixina(dotted, 2px);
+}
+
+.one-arg {
+ .mixin(3px);
+}
+
+.no-parens {
+ .mixin;
+}
+
+.no-args {
+ .mixin();
+}
+
+.var-args {
+ @var: 9;
+ .mixin(@var, (@var * 2));
+}
+
+.multi-mix {
+ .mixin(2px, 30%);
+ .mixiny(4, 5);
+}
+
+.maxa(@arg1: 10, @arg2: #f00) {
+ padding: (@arg1 * 2px);
+ color: @arg2;
+}
+
+body {
+ .maxa(15);
+}
+
+@glob: 5;
+.global-mixin(@a:2) {
+ width: (@glob + @a);
+}
+
+.scope-mix {
+ .global-mixin(3);
+}
+
+.nested-ruleset (@width: 200px) {
+ width: @width;
+ .column { margin: @width; }
+}
+.content {
+ .nested-ruleset(600px);
+}
+
+//
+
+.same-var-name2(@radius) {
+ radius: @radius;
+}
+.same-var-name(@radius) {
+ .same-var-name2(@radius);
+}
+#same-var-name {
+ .same-var-name(5px);
+}
+
+//
+
+.var-inside () {
+ @var: 10px;
+ width: @var;
+}
+#var-inside { .var-inside; }
+
+.mixin-arguments (@width: 0px, ...) {
+ border: @arguments;
+ width: @width;
+}
+
+.arguments {
+ .mixin-arguments(1px, solid, black);
+}
+.arguments2 {
+ .mixin-arguments();
+}
+.arguments3 {
+ .mixin-arguments;
+}
+
+.mixin-arguments2 (@width, @rest...) {
+ border: @arguments;
+ rest: @rest;
+ width: @width;
+}
+.arguments4 {
+ .mixin-arguments2(0, 1, 2, 3, 4);
+}
+
+// Edge cases
+
+.edge-case {
+ .mixin-arguments("{");
+}
+
+// Division vs. Literal Slash
+.border-radius(@r: 2px/5px) {
+ border-radius: @r;
+}
+.slash-vs-math {
+ .border-radius();
+ .border-radius(5px/10px);
+ .border-radius((3px * 2));
+}
+// semi-colon vs comma for delimiting
+
+.mixin-takes-one(@a) {
+ one: @a;
+}
+
+.mixin-takes-two(@a; @b) {
+ one: @a;
+ two: @b;
+}
+
+.comma-vs-semi-colon {
+ .mixin-takes-two(@a : a; @b : b, c);
+ .mixin-takes-two(@a : d, e; @b : f);
+ .mixin-takes-one(@a: g);
+ .mixin-takes-one(@a : h;);
+ .mixin-takes-one(i);
+ .mixin-takes-one(j;);
+ .mixin-takes-two(k, l);
+ .mixin-takes-one(m, n;);
+ .mixin-takes-two(o, p; q);
+ .mixin-takes-two(r, s; t;);
+}
+
+.mixin-conflict(@a:defA, @b:defB, @c:defC) {
+ three: @a, @b, @c;
+}
+
+.mixin-conflict(@a:defA, @b:defB, @c:defC, @d:defD) {
+ four: @a, @b, @c, @d;
+}
+
+#named-conflict {
+ .mixin-conflict(11, 12, 13, @a:a);
+ .mixin-conflict(@a:a, 21, 22, 23);
+}
+@a: 3px;
+.mixin-default-arg(@a: 1px, @b: @a, @c: @b) {
+ defaults: 1px 1px 1px;
+ defaults: 2px 2px 2px;
+}
+
+.test-mixin-default-arg {
+ .mixin-default-arg();
+ .mixin-default-arg(2px);
+}
+
+.mixin-comma-default1(@color; @padding; @margin: 2, 2, 2, 2) {
+ margin: @margin;
+}
+.selector {
+ .mixin-comma-default1(#33acfe; 4);
+}
+.mixin-comma-default2(@margin: 2, 2, 2, 2;) {
+ margin: @margin;
+}
+.selector2 {
+ .mixin-comma-default2();
+}
+.mixin-comma-default3(@margin: 2, 2, 2, 2) {
+ margin: @margin;
+}
+.selector3 {
+ .mixin-comma-default3(4,2,2,2);
+}
diff --git a/vendor/leafo/lessphp/tests/inputs_lessjs/mixins-named-args.less b/vendor/leafo/lessphp/tests/inputs_lessjs/mixins-named-args.less
new file mode 100644
index 00000000..f62dc86a
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs_lessjs/mixins-named-args.less
@@ -0,0 +1,36 @@
+.mixin (@a: 1px, @b: 50%) {
+ width: (@a * 5);
+ height: (@b - 1%);
+ args: @arguments;
+}
+.mixin (@a: 1px, @b: 50%) when (@b > 75%){
+ text-align: center;
+}
+
+.named-arg {
+ color: blue;
+ .mixin(@b: 100%);
+}
+
+.class {
+ @var: 20%;
+ .mixin(@b: @var);
+}
+
+.all-args-wrong-args {
+ .mixin(@b: 10%, @a: 2px);
+}
+
+.mixin2 (@a: 1px, @b: 50%, @c: 50) {
+ width: (@a * 5);
+ height: (@b - 1%);
+ color: (#000000 + @c);
+}
+
+.named-args2 {
+ .mixin2(3px, @c: 100);
+}
+
+.named-args3 {
+ .mixin2(@b: 30%, @c: #123456);
+}
diff --git a/vendor/leafo/lessphp/tests/inputs_lessjs/strings.less b/vendor/leafo/lessphp/tests/inputs_lessjs/strings.less
new file mode 100644
index 00000000..32fad721
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/inputs_lessjs/strings.less
@@ -0,0 +1,51 @@
+#strings {
+ background-image: url("http://son-of-a-banana.com");
+ quotes: "~" "~";
+ content: "#*%:&^,)!.(~*})";
+ empty: "";
+ brackets: "{" "}";
+ escapes: "\"hello\" \\world";
+ escapes2: "\"llo";
+}
+#comments {
+ content: "/* hello */ // not-so-secret";
+}
+#single-quote {
+ quotes: "'" "'";
+ content: '""#!&""';
+ empty: '';
+ semi-colon: ';';
+}
+#escaped {
+ filter: ~"DX.Transform.MS.BS.filter(opacity=50)";
+}
+#one-line { image: url(http://tooks.com) }
+#crazy { image: url(http://), "}", url("http://}") }
+#interpolation {
+ @var: '/dev';
+ url: "http://lesscss.org@{var}/image.jpg";
+
+ @var2: 256;
+ url2: "http://lesscss.org/image-@{var2}.jpg";
+
+ @var3: #456;
+ url3: "http://lesscss.org@{var3}";
+
+ @var4: hello;
+ url4: "http://lesscss.org/@{var4}";
+
+ @var5: 54.4px;
+ url5: "http://lesscss.org/@{var5}";
+}
+
+// multiple calls with string interpolation
+
+.mix-mul (@a: green) {
+ color: ~"@{a}";
+}
+.mix-mul-class {
+ .mix-mul(blue);
+ .mix-mul(red);
+ .mix-mul(black);
+ .mix-mul(orange);
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/accessors.css b/vendor/leafo/lessphp/tests/outputs/accessors.css
new file mode 100644
index 00000000..2f3c9e61
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/accessors.css
@@ -0,0 +1,14 @@
+.article { color:#294366; }
+.comment {
+ width:960px;
+ color:#294366;
+ padding:10px;
+}
+.wow {
+ height:960px;
+ background-color:#294366;
+ color:green;
+ padding:;
+ margin:;
+}
+.mix { font-size:10px; }
diff --git a/vendor/leafo/lessphp/tests/outputs/arity.css b/vendor/leafo/lessphp/tests/outputs/arity.css
new file mode 100644
index 00000000..5173561d
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/arity.css
@@ -0,0 +1,25 @@
+.one {
+ hello: one;
+ world: one;
+}
+.two {
+ hello: two;
+ world: two;
+}
+.three {
+ hello: three;
+ world: three;
+}
+.multi-foo {
+ foo: two cool;
+ foo: three cool yeah;
+ foo: two 1;
+ foo: three 1 yeah;
+ foo: three 1 1;
+}
+.multi-baz {
+ baz: two cool;
+ baz: three yeah;
+ baz: two 1;
+ baz: three 1;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/attributes.css b/vendor/leafo/lessphp/tests/outputs/attributes.css
new file mode 100644
index 00000000..fb0e176c
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/attributes.css
@@ -0,0 +1,105 @@
+* {
+ color: blue;
+}
+E {
+ color: blue;
+}
+E[foo] {
+ color: blue;
+}
+[foo] {
+ color: blue;
+}
+[foo] .helloWorld {
+ color: blue;
+}
+[foo].helloWorld {
+ color: blue;
+}
+E[foo="barbar"] {
+ color: blue;
+}
+E[foo~="hello#$@%@$#^"] {
+ color: blue;
+}
+E[foo^="color: green;"] {
+ color: blue;
+}
+E[foo$="239023"] {
+ color: blue;
+}
+E[foo*="29302"] {
+ color: blue;
+}
+E[foo|="239032"] {
+ color: blue;
+}
+E:root {
+ color: blue;
+}
+E:nth-child(odd) {
+ color: blue;
+}
+E:nth-child(2n+1) {
+ color: blue;
+}
+E:nth-child(5) {
+ color: blue;
+}
+E:nth-last-child(-n+2) {
+ color: blue;
+}
+E:nth-of-type(2n) {
+ color: blue;
+}
+E:nth-last-of-type(n) {
+ color: blue;
+}
+E:first-child {
+ color: blue;
+}
+E:last-child {
+ color: blue;
+}
+E:first-of-type {
+ color: blue;
+}
+E:last-of-type {
+ color: blue;
+}
+E:only-child {
+ color: blue;
+}
+E:only-of-type {
+ color: blue;
+}
+E:empty {
+ color: blue;
+}
+E:lang(en) {
+ color: blue;
+}
+E::first-line {
+ color: blue;
+}
+E::before {
+ color: blue;
+}
+E#id {
+ color: blue;
+}
+E:not(:link) {
+ color: blue;
+}
+E F {
+ color: blue;
+}
+E > F {
+ color: blue;
+}
+E + F {
+ color: blue;
+}
+E ~ F {
+ color: blue;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/builtins.css b/vendor/leafo/lessphp/tests/outputs/builtins.css
new file mode 100644
index 00000000..6ac21f2c
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/builtins.css
@@ -0,0 +1,61 @@
+body {
+ color: "hello world";
+ num-basic: 5.1666666666667;
+ num-floor: 5px;
+ num-ceil: 6px;
+ num2: 0.66666666666667;
+ num2-round: 1;
+ num2-floor: 0;
+ num2-ceil: 1;
+ round-lit: 3px;
+ rgba1: #ff112233;
+ rgba2: #992c3742;
+ argb: #992c3742;
+}
+format {
+ format: "rgb(32, 128, 64)";
+ format-string: "hello world";
+ format-multiple: "hello earth 2";
+ format-url-encode: 'red is %A';
+ eformat: rgb(32, 128, 64);
+}
+#functions {
+ str1: true;
+ str2: false;
+ num1: true;
+ num2: true;
+ num3: true;
+ num4: false;
+ col1: true;
+ col2: false;
+ col3: true;
+ col4: true;
+ key1: true;
+ key2: false;
+ px1: true;
+ px2: false;
+ per1: true;
+ per2: false;
+ em1: true;
+ em2: false;
+ ex1: 2;
+ ex2: 1;
+ ex3: extract(1,1);
+ ex4: 2;
+ pow: 16;
+ pi: 3.1415926535898;
+ mod: 4;
+ tan: 1.5574077246549;
+ cos: 0.54030230586814;
+ sin: 0.8414709848079;
+ atan: 0.78539816339745rad;
+ acos: 0rad;
+ asin: 1.5707963267949rad;
+ sqrt: 2.8284271247462;
+}
+#unit {
+ unit-lit: 10;
+ unit-arg: 10s;
+ unit-arg2: 10em;
+ unit-math: 7.407%;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/colors.css b/vendor/leafo/lessphp/tests/outputs/colors.css
new file mode 100644
index 00000000..5310ffb0
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/colors.css
@@ -0,0 +1,103 @@
+body {
+ color: #ff0000;
+ color: #ffffff;
+ color: #000000;
+ color: #996d33;
+ color: rgba(153,109,51,0.3);
+ lighten1: #ffffff;
+ lighten2: #7c8df2;
+ lighten3: rgba(222,140,129,0.5);
+ darken1: #d6d6d6;
+ darken2: #0d1e81;
+ darken3: rgba(18,42,185,0.5);
+ saturate1: #f1eded;
+ saturate2: #0025fe;
+ saturate3: rgba(10,44,244,0.5);
+ desaturate1: #efefef;
+ desaturate2: #3349cb;
+ desaturate3: rgba(36,62,218,0.5);
+ spin1: #efefef;
+ spin2: #2d17e7;
+ spin3: rgba(59,23,231,0.5);
+ spin2: #efefef;
+ spin3: #1769e7;
+ spin4: rgba(23,119,231,0.5);
+ one1: #abcdef;
+ one2: #abcdef;
+ two1: rgba(2,159,35,0.9);
+ two2: rgba(2,159,35,0.9);
+ three1: rgba(1,2,3,0.6);
+ three2: rgba(1,2,3,0.6);
+ four1: rgba(1,2,3,0);
+ four2: rgba(1,2,3,0);
+ hue: 282;
+ sat: 33;
+ lit: 12;
+ what: #dff1da;
+ zero1: #343434;
+ zero2: #003468;
+ zero3: #000000;
+ zero4: #ffffff;
+ zero5: #000000;
+ zero6: #ffffff;
+ zero7: #ffffff;
+ zero8: #ffffff;
+ zero9: #1d5612;
+ zeroa: #56124b;
+ zerob: #000000;
+ zeroc: #ffffff;
+}
+alpha {
+ g1: 0;
+ g2: 1;
+}
+fade {
+ f1: rgba(255,0,0,0.5);
+ f2: rgba(255,255,255,0.2);
+ f3: rgba(34,23,64,0.5);
+}
+.mix {
+ color1: #808080;
+ color2: #808080;
+ color3: rgba(6,3,2,-0.25);
+}
+.contrast {
+ color1: #ff0000;
+ color2: #0000ff;
+}
+.percent {
+ per: 50%;
+}
+.colorz {
+ color1: #ebebeb;
+ color2: #ff9100;
+ color3: #000000;
+}
+body {
+ start: #fcf8e3;
+ spin: #fcf4e3;
+ chained: #fbeed5;
+ direct: #fbefd5;
+}
+pre {
+ spin: #f2dee1;
+}
+dd {
+ background-color: #f5f5f5;
+}
+.ops {
+ c1: #4d0000;
+ c2: #004000;
+ c3: #020002;
+ c4: #ffffff;
+ c5: #fb8173;
+ c6: 132 / #ff0000;
+ c7: 132 - #ff0000;
+ c8: 132- #ff0000;
+}
+.transparent {
+ r: 0;
+ g: 0;
+ b: 0;
+ a: 0;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/compile_on_mixin.css b/vendor/leafo/lessphp/tests/outputs/compile_on_mixin.css
new file mode 100644
index 00000000..318526a5
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/compile_on_mixin.css
@@ -0,0 +1,29 @@
+body {
+ height: 22px;
+}
+body ul {
+ height: 20px;
+}
+body ul li {
+ height: 10px;
+}
+body ul li div span,
+body ul li link {
+ margin: 10px;
+ color: red;
+}
+body ul div,
+body ul p {
+ border: 1px;
+}
+body ul div.hello,
+body ul p.hello {
+ color: green;
+}
+body ul div :what,
+body ul p :what {
+ color: blue;
+}
+body ul a b {
+ color: blue;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/data-uri.css b/vendor/leafo/lessphp/tests/outputs/data-uri.css
new file mode 100644
index 00000000..e51b8f9c
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/data-uri.css
@@ -0,0 +1,6 @@
+.small {
+ background: url("data:text/plain;base64,CmRpdjpiZWZvcmUgewoJY29udGVudDogImhpISI7Cn0KCg==");
+}
+.large {
+ background: url("../../../lessc.inc.php");
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/directives.css b/vendor/leafo/lessphp/tests/outputs/directives.css
new file mode 100644
index 00000000..c2d82b83
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/directives.css
@@ -0,0 +1,27 @@
+@charset "utf-8";
+@-moz-document url-prefix() {
+ div {
+ color: red;
+ }
+}
+@page :left {
+ margin-left: 4cm;
+}
+@page :right {
+ margin-left: 3cm;
+}
+@page {
+ margin: 2cm;
+}
+@-ms-viewport {
+ width: device-width;
+}
+@-moz-viewport {
+ width: device-width;
+}
+@-o-viewport {
+ width: device-width;
+}
+@viewport {
+ width: device-width;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/escape.css b/vendor/leafo/lessphp/tests/outputs/escape.css
new file mode 100644
index 00000000..0587bcab
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/escape.css
@@ -0,0 +1,14 @@
+body {
+ e1: this is simple;
+ e2: this is simple;
+ e3: 1232;
+ e4: world;
+ e5: onemore;
+ t1: eating rice;
+ t2: string cheese;
+ t3: a b c string me d e f;
+ t4: string world;
+}
+.class {
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src='image.png');
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/font_family.css b/vendor/leafo/lessphp/tests/outputs/font_family.css
new file mode 100644
index 00000000..fc260fd4
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/font_family.css
@@ -0,0 +1,17 @@
+@font-face {
+ font-family: Graublau Sans Web;
+ src: url(fonts/GraublauWeb.otf) format("opentype");
+}
+@font-face {
+ font-family: Gentium;
+ src: url('fonts/Gentium.ttf');
+}
+@font-face {
+ font-family: Gentium;
+ src: url("fonts/GentiumItalic.ttf");
+ font-style: italic;
+}
+h2 {
+ font-family: Gentium;
+ crazy: maroon;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/guards.css b/vendor/leafo/lessphp/tests/outputs/guards.css
new file mode 100644
index 00000000..34af5495
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/guards.css
@@ -0,0 +1,27 @@
+dd {
+ simple: yellow;
+}
+b {
+ something: red;
+ something-complex: blue cool;
+ something-complex: blue birthday;
+}
+img {
+ another: green;
+ flipped: teal;
+}
+body {
+ yeah-number: purple 232px;
+ yeah-pixel: silver;
+ yeah-number: purple 232;
+}
+div {
+ something-complex: blue true;
+}
+link {
+ color: true red;
+ color: true #fff;
+ color: true #fffddd;
+ color: true #000000;
+ color: true rgba(0,0,0,0.34);
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/hacks.css b/vendor/leafo/lessphp/tests/outputs/hacks.css
new file mode 100644
index 00000000..b8327eb5
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/hacks.css
@@ -0,0 +1,4 @@
+:root .alert-message,
+:root .btn {
+ border-radius: 0 \0;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/hi.css b/vendor/leafo/lessphp/tests/outputs/hi.css
new file mode 100644
index 00000000..3bffcf0b
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/hi.css
@@ -0,0 +1,3 @@
+div:before {
+ content: "hi!";
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/ie.css b/vendor/leafo/lessphp/tests/outputs/ie.css
new file mode 100644
index 00000000..7e4571c8
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/ie.css
@@ -0,0 +1,9 @@
+foo {
+ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr=#c0ff3300,endColorstr=#ff000000);
+ filter: progid:DXImageTransform.Microsoft.gradient(GradientType=1,startColorstr=#c0ff3300,endColorstr=#ff000001);
+}
+foo {
+ filter: alpha(opacity=20);
+ filter: alpha(opacity=20,enabled=true);
+ filter: blaznicate(foo=bar,baz=bang bip,bart=#fa4600);
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/import.css b/vendor/leafo/lessphp/tests/outputs/import.css
new file mode 100644
index 00000000..b76c25b5
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/import.css
@@ -0,0 +1,51 @@
+@import "not-found";
+@import "something.css" media;
+@import url("something.css") media;
+@import url(something.css) media, screen, print;
+b {
+ color: maroon;
+ padding: 16px;
+}
+body {
+ line-height: 10em;
+}
+body div.bright {
+ color: red;
+}
+body div.sad {
+ color: blue;
+}
+.one {
+ color: blue;
+}
+#merge-import-mixins .just-a-class {
+ background: red;
+}
+#merge-import-mixins .just-a-class {
+ background: blue;
+}
+#merge-import-mixins .hello {
+ height: 200px;
+}
+@media cool {
+ #merge-import-mixins {
+ color: red;
+ height: 200px;
+ }
+}
+#merge-import-mixins div {
+ background: red;
+ background: blue;
+}
+.inner {
+ content: "inner/file1.less";
+}
+.inner {
+ content: "inner/file2.less";
+}
+pre {
+ color: hello-from-file-3;
+}
+h2 {
+ background: url("../images/logo.png") no-repeat;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/interpolation.css b/vendor/leafo/lessphp/tests/outputs/interpolation.css
new file mode 100644
index 00000000..c3ff1f42
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/interpolation.css
@@ -0,0 +1,28 @@
+div {
+ interp1: yes;
+ interp2: yes;
+ interp3: okay;
+}
+10"yeah" {
+ color: blue;
+}
+10 {
+ color: blue;
+}
+hello world 10 {
+ color: red;
+}
+#"yeah" {
+ color: "hello 10";
+}
+[prop],
+[prop="value3"],
+[prop*="val3"],
+[|prop~="val3"],
+[*|prop$="val3"],
+[ns|prop^="val3"],
+[3^="val3"],
+[3=3],
+[3] {
+ attributes: yes;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/keyframes.css b/vendor/leafo/lessphp/tests/outputs/keyframes.css
new file mode 100644
index 00000000..cf62be11
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/keyframes.css
@@ -0,0 +1,48 @@
+@keyframes 'bounce' {
+ from {
+ top: 100px;
+ animation-timing-function: ease-out;
+ }
+ 25% {
+ top: 50px;
+ animation-timing-function: ease-in;
+ }
+ 50% {
+ top: 100px;
+ animation-timing-function: ease-out;
+ }
+ 75% {
+ top: 75px;
+ animation-timing-function: ease-in;
+ }
+ to {
+ top: 100px;
+ }
+}
+@-webkit-keyframes flowouttoleft {
+ 0% {
+ -webkit-transform: translateX(0) scale(1);
+ }
+ 60%,
+ 70% {
+ -webkit-transform: translateX(0) scale(.7);
+ }
+ 100% {
+ -webkit-transform: translateX(-100%) scale(.7);
+ }
+}
+div {
+ animation-name: 'diagonal-slide';
+ animation-duration: 5s;
+ animation-iteration-count: 10;
+}
+@keyframes 'diagonal-slide' {
+ from {
+ left: 0;
+ top: 0;
+ }
+ to {
+ left: 100px;
+ top: 100px;
+ }
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/math.css b/vendor/leafo/lessphp/tests/outputs/math.css
new file mode 100644
index 00000000..8d425f30
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/math.css
@@ -0,0 +1,69 @@
+.unary {
+ sub: 10 -5;
+}
+.spaces {
+ sub1: 5;
+ sub2: 5;
+ add1: 15;
+ add2: 15;
+ div: 2;
+ mul1: 50;
+ mul2: 50;
+}
+.supress-division {
+ border-radius: 10px/10px;
+ border-radius: 10px/12px;
+ border-radius: hello(10px/10px) world;
+ font: 10px/30 sans-serif;
+ font: 10px/20px sans-serif;
+ font: 10px/22px sans-serif;
+ border-radius: 0 15px 15px 15px/0 50% 50% 50%;
+}
+.parens {
+ sub: 5;
+ add: 15;
+ div1: 2;
+ div2: 2;
+ mul: 50;
+}
+.keyword-names {
+ height: "hello" 25;
+}
+.negation {
+ neg1: -1px;
+ neg2: -1px;
+ neg3: -10;
+}
+.test {
+ single1: 5;
+ single2: 10;
+ single3: 10;
+ parens: 10 -2;
+ math1: 20;
+ math2: 20;
+ complex1: 71;
+ complex2: 6;
+ complex3: 6px 1em 2px 2;
+ var1: 8 4 4 4px;
+ var2: 96;
+ var3: 12;
+ complex4: 113;
+}
+.percents {
+ p1: 1000%;
+ p2: 1000%;
+ p3: 100%;
+ p4: 1000px;
+ p5: 1000%;
+ p6: 30%;
+ p7: 10%;
+ p8: 2%;
+}
+.misc {
+ x: 40px;
+ y: 40em;
+}
+.cond {
+ c1: false;
+ c2: true;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/media.css b/vendor/leafo/lessphp/tests/outputs/media.css
new file mode 100644
index 00000000..99da8c31
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/media.css
@@ -0,0 +1,70 @@
+@media screen,3D {
+ P {
+ color: green;
+ }
+}
+@media print {
+ body {
+ font-size: 10pt;
+ }
+}
+@media screen {
+ body {
+ font-size: 13px;
+ }
+}
+@media screen,print {
+ body {
+ line-height: 1.2;
+ }
+}
+@media all and (min-width: 0px) {
+ body {
+ line-height: 1.2;
+ }
+}
+@media all and (min-width: 0) {
+ body {
+ line-height: 1.2;
+ }
+}
+@media screen and (min-width: 102.5em) and (max-width: 117.9375em),screen and (min-width: 150em) {
+ body {
+ color: blue;
+ }
+}
+@media screen and (min-height: 110px) {
+ body {
+ color: red;
+ }
+}
+@media screen and (height: 100px) and (width: 110px),(size: 120px) {
+ body {
+ color: red;
+ }
+}
+@media test {
+ div {
+ height: 20px;
+ }
+}
+@media test and (hello) {
+ div {
+ color: red;
+ }
+ div pre {
+ color: orange;
+ }
+}
+@media yeah {
+ @page {
+ @media cool {
+ color: red;
+ }
+ }
+}
+@media (max-width: 599px) {
+ .helloworld {
+ color: blue;
+ }
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/misc.css b/vendor/leafo/lessphp/tests/outputs/misc.css
new file mode 100644
index 00000000..6c99cc39
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/misc.css
@@ -0,0 +1,68 @@
+color: "aaa, bbb";
+.topbar {
+ background: url(/assets/images/test/topbar.png);
+}
+.hello {
+ test: empty-function("/assets/images/test/",40%,to(#fff));
+}
+.css3 {
+ background-image: -webkit-gradient(linear,0% 0%,0% 90%,from(#E9A000),to(#A37000));
+}
+.test,
+.world {
+ border: 1px solid red;
+ color: url(http://mage-page.com);
+ string: "hello /* this is not a comment */";
+ world: "// neither is this";
+ string: 'hello /* this is not a comment */';
+ world: '// neither is this';
+ what-ever: 100px;
+ background: url(/*no comment here*/);
+}
+.urls {
+ background1: url("http://google.com");
+ background2: url(http://google.com);
+ background3: url("http://google.com");
+}
+.cool {
+ color: "aaa, bbb";
+}
+.span-17 {
+ float: left;
+}
+.span-17 {
+ width: 660px;
+}
+.x {
+ float: left;
+ width: 660px;
+}
+.hi pre {
+ color: red;
+}
+.hi pre {
+ color: blue;
+}
+.rad pre {
+ color: red;
+}
+.rad pre {
+ color: blue;
+}
+hello {
+ numbers: 1.0 0.1 .1 1.;
+ numbers: 1.0s 0.1s .1s 1.s;
+ numbers: -1s -0.1s -0.1s -1s;
+ numbers: -1 -0.1 -0.1 -1;
+}
+#string {
+ hello: 'what\'s going on here';
+ hello: 'blah blag @{ blah blah';
+ join: "3434hello";
+ join: 3434hello;
+}
+.duplicates {
+ hello: world;
+ hello: "world";
+ hello: "what";
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/mixin_functions.css b/vendor/leafo/lessphp/tests/outputs/mixin_functions.css
new file mode 100644
index 00000000..53785803
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/mixin_functions.css
@@ -0,0 +1,7 @@
+body {
+ padding: 2.0em;
+ color: red;
+ margin: 10px;
+ height: 12px;
+ border-bottom: 1px solid green;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/mixin_merging.css b/vendor/leafo/lessphp/tests/outputs/mixin_merging.css
new file mode 100644
index 00000000..f396ba92
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/mixin_merging.css
@@ -0,0 +1,42 @@
+#test1 div {
+ color:red;
+ height:10px;
+}
+#test1 p { height:10px; }
+#test2 b {
+ color:red;
+ width:1px;
+}
+#test2 a, #test2 i { width:1px; }
+#test3 a, #test3 i { width:1px; }
+#test3 b {
+ width:1px;
+ color:red;
+}
+#test4 a {
+ color:blue;
+ margin:1px;
+}
+#test4 div, #test4 html { color:blue; }
+#test5 img, #test5 strong {
+ padding:2px;
+ float:right;
+}
+#test6 div a, #test6 span a {
+ line-height:10px;
+ color:red;
+}
+#test7 div strong {
+ margin:1px;
+ color:red;
+}
+#test7 div b { color:red; }
+#test7 span strong, #test7 span b { color:red; }
+#test8 a i, #test8 b i {
+ background:red;
+ color:red;
+}
+#test8 a s, #test8 b s {
+ background:red;
+ color:blue;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/mixins.css b/vendor/leafo/lessphp/tests/outputs/mixins.css
new file mode 100644
index 00000000..cce87eb4
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/mixins.css
@@ -0,0 +1,92 @@
+.bold {
+ font-size: 20px;
+ font-weight: bold;
+}
+body #window {
+ border-radius: 10px;
+ font-size: 20px;
+ font-weight: bold;
+ line-height: 30px;
+}
+#bundle .button {
+ display: block;
+ border: 1px solid black;
+ background-color: grey;
+}
+#bundle .button:hover {
+ background-color: white;
+}
+#header a {
+ color: orange;
+ display: block;
+ border: 1px solid black;
+ background-color: grey;
+}
+#header a:hover {
+ background-color: white;
+}
+div {
+ color: blue;
+ hello: world;
+}
+div b {
+ color: blue;
+}
+body {
+ color: blue;
+ hello: world;
+}
+body b {
+ color: blue;
+}
+.hello .world {
+ color: blue;
+}
+.foobar {
+ color: blue;
+}
+.eggs {
+ foo: 1px 2px;
+ bar: 1px 2px;
+ foo: 100 land;
+ bar: 100 land;
+}
+#hello {
+ cool: one two three cool;
+}
+#hello-important {
+ cool: one two three cool !important;
+}
+#world {
+ cool: "world";
+}
+#another {
+ things: red blue green;
+ things: red blue green skip me;
+}
+#day .cool {
+ color: one two three;
+}
+#day .cool {
+ color: one two three skip me;
+}
+.mix-suffix {
+ color: red !important;
+ height: 20px !important;
+}
+.mix-suffix pre {
+ color: red;
+}
+.search-test {
+ color: #f00 !important;
+ color: #0f0 !important;
+}
+.cowboy {
+ color: blue;
+}
+.nav .nav-divider {
+ padding: 10px;
+}
+.nav-divider {
+ padding: 10px;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/nested.css b/vendor/leafo/lessphp/tests/outputs/nested.css
new file mode 100644
index 00000000..454dcfcc
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/nested.css
@@ -0,0 +1,51 @@
+#header {
+ color: black;
+}
+#header .navigation {
+ font-size: 12px;
+}
+#header .navigation .border .outside {
+ color: blue;
+}
+#header .logo {
+ width: 300px;
+}
+#header .logo:hover {
+ text-decoration: none;
+}
+a b ul li {
+ color: green;
+}
+div .cool {
+ color: green;
+}
+p .cool span {
+ color: yellow;
+}
+div another {
+ color: green;
+}
+p another span {
+ color: yellow;
+}
+b .something {
+ color: blue;
+}
+b.something {
+ color: blue;
+}
+.foo .bar .qux,
+.foo .baz .qux {
+ display: block;
+}
+.qux .foo .bar,
+.qux .foo .baz {
+ display: inline;
+}
+.qux .foo .bar .biz,
+.qux .foo .baz .biz {
+ display: none;
+}
+b hello [x="&yeah"] {
+ color: red;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/nesting.css b/vendor/leafo/lessphp/tests/outputs/nesting.css
new file mode 100644
index 00000000..804a56bf
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/nesting.css
@@ -0,0 +1,6 @@
+#header .navigation .border .outside { color:blue; }
+#header .navigation { font-size:12px; }
+#header .logo:hover { text-decoration:none; }
+#header .logo { width:300px; }
+#header { color:black; }
+a b ul li { color:green; }
diff --git a/vendor/leafo/lessphp/tests/outputs/pattern_matching.css b/vendor/leafo/lessphp/tests/outputs/pattern_matching.css
new file mode 100644
index 00000000..215371b0
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/pattern_matching.css
@@ -0,0 +1,72 @@
+.class {
+ color: #a2a2a2;
+ display: block;
+}
+.zero {
+ zero: 0;
+ one: 1;
+ two: 2;
+ three: 3;
+}
+.one {
+ one: 1;
+ one-req: 1;
+ two: 2;
+ three: 3;
+}
+.two {
+ two: 2;
+ three: 3;
+}
+.three {
+ three-req: 3;
+ three: 3;
+}
+.left {
+ left: 1;
+}
+.right {
+ right: 1;
+}
+.border-right {
+ color: black;
+ border-right: 4px;
+}
+.border-left {
+ color: black;
+ border-left: 4px;
+}
+.only-right {
+ right: 33;
+}
+.only-left {
+ left: 33;
+}
+.left-right {
+ both: 330;
+}
+#hola {
+ color: blue;
+}
+#defaults_1 {
+ height: one;
+ height: two;
+ height: three;
+ height: four;
+}
+.thing {
+ color: red;
+}
+#aa {
+ color: green;
+ color: blue;
+ color: red;
+}
+#bb {
+ color: green;
+ color: blue;
+ color: red;
+}
+#cc {
+ color: blue;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/scopes.css b/vendor/leafo/lessphp/tests/outputs/scopes.css
new file mode 100644
index 00000000..ea2a4573
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/scopes.css
@@ -0,0 +1,11 @@
+body div other world {
+ height: 50;
+}
+div other world {
+ height: 50;
+}
+pre {
+ height: 10;
+ height: 11;
+ height: 12;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/selector_expressions.css b/vendor/leafo/lessphp/tests/outputs/selector_expressions.css
new file mode 100644
index 00000000..71d4a5d8
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/selector_expressions.css
@@ -0,0 +1,25 @@
+something blue,
+world {
+ color: blue;
+}
+.div (3434) {
+ height: 100px;
+}
+.div cool red {
+ height: 4000px;
+}
+.span5 {
+ color: 15;
+}
+.span4 {
+ color: 14;
+}
+.span3 {
+ color: 13;
+}
+.span2 {
+ color: 12;
+}
+.span1 {
+ color: 11;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/site_demos.css b/vendor/leafo/lessphp/tests/outputs/site_demos.css
new file mode 100644
index 00000000..3428ad3a
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/site_demos.css
@@ -0,0 +1,76 @@
+default .underline {
+ border-bottom: 1px solid green;
+}
+default #header {
+ color: black;
+ border: 1px solid #dd44dd;
+}
+default #header .navigation {
+ font-size: 12px;
+}
+default #header .navigation a {
+ border-bottom: 1px solid green;
+}
+default #header .logo {
+ width: 300px;
+}
+default #header .logo:hover {
+ text-decoration: none;
+}
+variables .variables {
+ width: 14cm;
+ height: 24px;
+ color: #888;
+ background: #6c94be;
+ font-family: "Trebuchet MS", Verdana, sans-serif;
+}
+mixins .bordered {
+ border-top: dotted 1px black;
+ border-bottom: solid 2px black;
+}
+mixins #menu a {
+ color: #111;
+ border-top: dotted 1px black;
+ border-bottom: solid 2px black;
+}
+mixins .post a {
+ color: red;
+ border-top: dotted 1px black;
+ border-bottom: solid 2px black;
+}
+nested-rules #header {
+ color: black;
+}
+nested-rules #header .navigation {
+ font-size: 12px;
+}
+nested-rules #header .logo {
+ width: 300px;
+}
+nested-rules #header .logo:hover {
+ text-decoration: none;
+}
+namespaces #bundle .button {
+ display: block;
+ border: 1px solid black;
+ background-color: grey;
+}
+namespaces #bundle .button:hover {
+ background-color: white;
+}
+namespaces #header a {
+ color: orange;
+ display: block;
+ border: 1px solid black;
+ background-color: grey;
+}
+namespaces #header a:hover {
+ background-color: white;
+}
+mixin-functions body {
+ padding: 2.0em;
+ color: red;
+ margin: 10px;
+ height: 12px;
+ border-bottom: 1px solid green;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs/variables.css b/vendor/leafo/lessphp/tests/outputs/variables.css
new file mode 100644
index 00000000..c4857cc6
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs/variables.css
@@ -0,0 +1,19 @@
+outer1: 44px;
+outer2: 44px;
+.variables {
+ width: 14cm;
+ height: 24px;
+ margin-top: -20px;
+ margin-bottom: 30px;
+ color: #888899;
+ background: #6c94be;
+ font-family: "Trebuchet MS", Verdana, sans-serif;
+ margin: 3px;
+ font: 10px/12px serif;
+ font: 120%/120% serif;
+}
+.external {
+ color: #888;
+ border: 1px solid #3326cc;
+ background: rgba(23,68,149,0.5);
+}
diff --git a/vendor/leafo/lessphp/tests/outputs_lessjs/mixins-args.css b/vendor/leafo/lessphp/tests/outputs_lessjs/mixins-args.css
new file mode 100644
index 00000000..0a8e6bee
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs_lessjs/mixins-args.css
@@ -0,0 +1,113 @@
+#hidden {
+ color: transparent;
+}
+#hidden1 {
+ color: transparent;
+}
+.two-args {
+ color: blue;
+ width: 10px;
+ height: 99%;
+ border: 2px dotted black;
+}
+.one-arg {
+ width: 15px;
+ height: 49%;
+}
+.no-parens {
+ width: 5px;
+ height: 49%;
+}
+.no-args {
+ width: 5px;
+ height: 49%;
+}
+.var-args {
+ width: 45;
+ height: 17%;
+}
+.multi-mix {
+ width: 10px;
+ height: 29%;
+ margin: 4;
+ padding: 5;
+}
+body {
+ padding: 30px;
+ color: #f00;
+}
+.scope-mix {
+ width: 8;
+}
+.content {
+ width: 600px;
+}
+.content .column {
+ margin: 600px;
+}
+#same-var-name {
+ radius: 5px;
+}
+#var-inside {
+ width: 10px;
+}
+.arguments {
+ border: 1px solid black;
+ width: 1px;
+}
+.arguments2 {
+ border: 0px;
+ width: 0px;
+}
+.arguments3 {
+ border: 0px;
+ width: 0px;
+}
+.arguments4 {
+ border: 0 1 2 3 4;
+ rest: 1 2 3 4;
+ width: 0;
+}
+.edge-case {
+ border: "{";
+ width: "{";
+}
+.slash-vs-math {
+ border-radius: 0.4px;
+ border-radius: 0.5px;
+ border-radius: 6px;
+}
+.comma-vs-semi-colon {
+ one: a;
+ two: b, c;
+ one: d, e;
+ two: f;
+ one: g;
+ one: h;
+ one: i;
+ one: j;
+ one: k;
+ two: l;
+ one: m, n;
+ one: o, p;
+ two: q;
+ one: r, s;
+ two: t;
+}
+#named-conflict {
+ four: a, 11, 12, 13;
+ four: a, 21, 22, 23;
+}
+.test-mixin-default-arg {
+ defaults: 1px 1px 1px;
+ defaults: 2px 2px 2px;
+}
+.selector {
+ margin: 2, 2, 2, 2;
+}
+.selector2 {
+ margin: 2, 2, 2, 2;
+}
+.selector3 {
+ margin: 4;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs_lessjs/mixins-named-args.css b/vendor/leafo/lessphp/tests/outputs_lessjs/mixins-named-args.css
new file mode 100644
index 00000000..e460aa10
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs_lessjs/mixins-named-args.css
@@ -0,0 +1,27 @@
+.named-arg {
+ color: blue;
+ width: 5px;
+ height: 99%;
+ args: 1px 100%;
+ text-align: center;
+}
+.class {
+ width: 5px;
+ height: 19%;
+ args: 1px 20%;
+}
+.all-args-wrong-args {
+ width: 10px;
+ height: 9%;
+ args: 2px 10%;
+}
+.named-args2 {
+ width: 15px;
+ height: 49%;
+ color: #646464;
+}
+.named-args3 {
+ width: 5px;
+ height: 29%;
+ color: #123456;
+}
diff --git a/vendor/leafo/lessphp/tests/outputs_lessjs/strings.css b/vendor/leafo/lessphp/tests/outputs_lessjs/strings.css
new file mode 100644
index 00000000..f7b9c27d
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/outputs_lessjs/strings.css
@@ -0,0 +1,40 @@
+#strings {
+ background-image: url("http://son-of-a-banana.com");
+ quotes: "~" "~";
+ content: "#*%:&^,)!.(~*})";
+ empty: "";
+ brackets: "{" "}";
+ escapes: "\"hello\" \\world";
+ escapes2: "\"llo";
+}
+#comments {
+ content: "/* hello */ // not-so-secret";
+}
+#single-quote {
+ quotes: "'" "'";
+ content: '""#!&""';
+ empty: '';
+ semi-colon: ';';
+}
+#escaped {
+ filter: DX.Transform.MS.BS.filter(opacity=50);
+}
+#one-line {
+ image: url(http://tooks.com);
+}
+#crazy {
+ image: url(http://), "}", url("http://}");
+}
+#interpolation {
+ url: "http://lesscss.org/dev/image.jpg";
+ url2: "http://lesscss.org/image-256.jpg";
+ url3: "http://lesscss.org#445566";
+ url4: "http://lesscss.org/hello";
+ url5: "http://lesscss.org/54.4px";
+}
+.mix-mul-class {
+ color: blue;
+ color: red;
+ color: black;
+ color: orange;
+}
diff --git a/vendor/leafo/lessphp/tests/sort.php b/vendor/leafo/lessphp/tests/sort.php
new file mode 100644
index 00000000..70b907ea
--- /dev/null
+++ b/vendor/leafo/lessphp/tests/sort.php
@@ -0,0 +1,57 @@
+<?php
+error_reporting(E_ALL);
+
+require realpath(dirname(__FILE__)).'/../lessc.inc.php';
+
+// sorts the selectors in stylesheet in order to normalize it for comparison
+
+$exe = array_shift($argv); // remove filename
+
+if (!$fname = array_shift($argv)) {
+ $fname = "php://stdin";
+}
+
+class lesscNormalized extends lessc {
+ public $numberPrecision = 3;
+
+ public function compileValue($value) {
+ if ($value[0] == "raw_color") {
+ $value = $this->coerceColor($value);
+ }
+
+ return parent::compileValue($value);
+ }
+}
+
+class SortingFormatter extends lessc_formatter_lessjs {
+ function sortKey($block) {
+ if (!isset($block->sortKey)) {
+ sort($block->selectors, SORT_STRING);
+ $block->sortKey = implode(",", $block->selectors);
+ }
+
+ return $block->sortKey;
+ }
+
+ function sortBlock($block) {
+ usort($block->children, function($a, $b) {
+ $sort = strcmp($this->sortKey($a), $this->sortKey($b));
+ if ($sort == 0) {
+ // TODO
+ }
+ return $sort;
+ });
+
+ }
+
+ function block($block) {
+ $this->sortBlock($block);
+ return parent::block($block);
+ }
+
+}
+
+$less = new lesscNormalized();
+$less->setFormatter(new SortingFormatter);
+echo $less->parse(file_get_contents($fname));
+
diff --git a/vendor/liuggio/statsd-php-client/LICENSE b/vendor/liuggio/statsd-php-client/LICENSE
new file mode 100644
index 00000000..3af47475
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) Giulio De Donato
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/liuggio/statsd-php-client/Readme.md b/vendor/liuggio/statsd-php-client/Readme.md
new file mode 100644
index 00000000..81083e05
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/Readme.md
@@ -0,0 +1,149 @@
+## statsd-php-client
+
+Be careful, see the [Upgrading section](Readme.md#upgrade) for <= v1.0.4, there's a BC.
+
+[![Build Status](https://secure.travis-ci.org/liuggio/statsd-php-client.png)](http://travis-ci.org/liuggio/statsd-php-client) [![Latest Stable Version](https://poser.pugx.org/liuggio/statsd-php-client/v/stable.png)](https://packagist.org/packages/liuggio/statsd-php-client) [![Total Downloads](https://poser.pugx.org/liuggio/statsd-php-client/downloads.png)](https://packagist.org/packages/liuggio/statsd-php-client)
+
+`statsd-php-client` is an Open Source, and **Object Oriented** Client for **etsy/statsd** written in php
+
+- `StatsdDataFactory` creates the `Liuggio\StatsdClient\Entity\StatsdDataInterface` Objects
+
+- `Sender` just sends data over the network (there are many sender)
+
+- `StatsdClient` sends the created objects via the `Sender` to the server
+
+## Why use this library instead the [statsd/php-example](https://github.com/etsy/statsd/blob/master/examples/php-example.php)?
+
+- You are wise.
+
+- You could also use monolog to redirect data to statsd
+
+- This library is tested.
+
+- This library optimizes the messages to send, compressing multiple messages in individual UDP packets.
+
+- This library pays attention to the maximum length of the UDP.
+
+- This library is made by Objects not array, but it also accepts array.
+
+- You do want to debug the packets, and using `SysLogSender` the packets will be logged in your `syslog` log (on debian-like distro: `tail -f /var/log/syslog`)
+
+
+## Example
+
+1. create the Sender
+
+2. create the Client
+
+3. create the Factory
+
+4. the Factory will help you to create data
+
+5. the Client will send the data
+
+### Standard Usage
+
+```php
+use Liuggio\StatsdClient\StatsdClient,
+ Liuggio\StatsdClient\Factory\StatsdDataFactory,
+ Liuggio\StatsdClient\Sender\SocketSender;
+// use Liuggio\StatsdClient\Sender\SysLogSender;
+
+$sender = new SocketSender(/*'localhost', 8126, 'udp'*/);
+// $sender = new SysLogSender(); // enabling this, the packet will not send over the socket
+
+$client = new StatsdClient($sender);
+$factory = new StatsdDataFactory('\Liuggio\StatsdClient\Entity\StatsdData');
+
+// create the data with the factory
+$data[] = $factory->timing('usageTime', 100);
+$data[] = $factory->increment('visitor');
+$data[] = $factory->decrement('click');
+$data[] = $factory->gauge('gaugor', 333);
+$data[] = $factory->set('uniques', 765);
+
+// send the data as array or directly as object
+$client->send($data);
+```
+
+### Usage with Monolog
+
+```php
+use Liuggio\StatsdClient\StatsdClient,
+ Liuggio\StatsdClient\Factory\StatsdDataFactory,
+ Liuggio\StatsdClient\Sender\SocketSender;
+// use Liuggio\StatsdClient\Sender\SysLogSender;
+
+use Monolog\Logger;
+use Liuggio\StatsdClient\Monolog\Handler\StatsDHandler;
+
+$sender = new SocketSender(/*'localhost', 8126, 'udp'*/);
+// $sender = new SysLogSender(); // enabling this, the packet will not send over the socket
+$client = new StatsdClient($sender);
+$factory = new StatsdDataFactory();
+
+$logger = new Logger('my_logger');
+$logger->pushHandler(new StatsDHandler($client, $factory, 'prefix', Logger::DEBUG));
+
+$logger->addInfo('My logger is now ready');
+```
+
+the output will be: `prefix.my_logger.INFO.My-logger:1|c" 36 Bytes`
+
+
+
+
+## Short Theory
+
+### Easily Install StatSD and Graphite
+
+In order to try this application monitor you have to install etsy/statsd and Graphite
+
+see this blog post to install it with vagrant [Easy install statsd graphite](http://welcometothebundle.com/easily-install-statsd-and-graphite-with-vagrant/).
+
+#### [StatsD](https://github.com/etsy/statsd)
+
+StatsD is a simple daemon for easy stats aggregation
+
+#### [Graphite](http://graphite.wikidot.com/)
+
+Graphite is a Scalable Realtime Graphing
+
+#### The Client sends data with UDP (faster)
+
+https://www.google.com/search?q=tcp+vs+udp
+
+## Contribution
+
+Active contribution and patches are very welcome.
+To keep things in shape we have quite a bunch of unit tests. If you're submitting pull requests please
+make sure that they are still passing and if you add functionality please
+take a look at the coverage as well it should be pretty high :)
+
+- First fork or clone the repository
+
+```
+git clone git://github.com/liuggio/statsd-php-client.git
+cd statsd-php-client
+```
+
+- Install vendors:
+
+``` bash
+composer.phar install
+```
+
+- This will give you proper results:
+
+``` bash
+phpunit --coverage-html reports
+```
+
+## Upgrade
+
+BC from the v1.0.4 version, [see Sender and Client differences](https://github.com/liuggio/statsd-php-client/pull/5/files).
+
+
+## TODO
+
+example with monolog
diff --git a/vendor/liuggio/statsd-php-client/composer.json b/vendor/liuggio/statsd-php-client/composer.json
new file mode 100644
index 00000000..705dbdf8
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "liuggio/statsd-php-client",
+ "description": "Statsd (Object Oriented) client library for PHP",
+ "keywords": ["statsd", "monitoring", "etsy", "php"],
+ "homepage": "https://github.com/liuggio/statsd-php-client/",
+ "type": "library",
+ "license": "MIT",
+ "require": {
+ "php": ">=5.2"
+ },
+ "authors": [
+ {
+ "name": "Giulio De Donato",
+ "email": "liuggio@gmail.com"
+ }
+ ],
+ "autoload": {
+ "psr-0": {"Liuggio": "src/"}
+ },
+ "require-dev": {
+ "monolog/monolog": ">=1.2.0"
+ },
+ "suggest": {
+ "monolog/monolog": "Monolog, in order to do generate statistic from log >=1.2.0)"
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/phpunit.xml.dist b/vendor/liuggio/statsd-php-client/phpunit.xml.dist
new file mode 100644
index 00000000..621efd75
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/phpunit.xml.dist
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<phpunit bootstrap="tests/bootstrap.php" colors="true">
+ <testsuites>
+ <testsuite name="Statsd-Service Test Suite">
+ <directory>tests/Liuggio/StatsdClient</directory>
+ </testsuite>
+ </testsuites>
+
+ <filter>
+ <whitelist>
+ <directory suffix=".php">src/Liuggio/</directory>
+ </whitelist>
+ </filter>
+</phpunit> \ No newline at end of file
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdData.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdData.php
new file mode 100644
index 00000000..9c6203db
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdData.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace Liuggio\StatsdClient\Entity;
+
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+
+class StatsdData implements StatsdDataInterface
+{
+
+ private $key;
+ private $value;
+ private $metric;
+
+ /**
+ * @param string $key
+ */
+ public function setKey($key)
+ {
+ $this->key = $key;
+ }
+
+ /**
+ * @return string
+ */
+ public function getKey()
+ {
+ return $this->key;
+ }
+
+ /**
+ * @param int $value
+ */
+ public function setValue($value)
+ {
+ $this->value = $value;
+ }
+
+ /**
+ * @return int
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+
+ public function setMetric($metric)
+ {
+ $this->metric = $metric;
+ }
+
+ public function getMetric()
+ {
+ return $this->metric;
+ }
+
+ /**
+ * @param bool $withMetric
+ *
+ * @return string
+ */
+ public function getMessage($withMetric = true)
+ {
+ if (!$withMetric) {
+ return sprintf('%s:%s', $this->getKey(), $this->getValue());
+ } else {
+ return sprintf('%s:%s|%s', $this->getKey(), $this->getValue(), $this->getMetric());
+ }
+ }
+
+ /**
+ * @return string
+ */
+ public function __toString()
+ {
+ return $this->getMessage();
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdDataInterface.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdDataInterface.php
new file mode 100644
index 00000000..846bc7cc
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Entity/StatsdDataInterface.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace Liuggio\StatsdClient\Entity;
+
+interface StatsdDataInterface
+{
+ CONST STATSD_METRIC_TIMING = 'ms';
+ CONST STATSD_METRIC_GAUGE = 'g';
+ CONST STATSD_METRIC_SET = 's';
+ CONST STATSD_METRIC_COUNT = 'c';
+
+ /**
+ * @abstract
+ * @return string
+ */
+ function getKey();
+
+ /**
+ * @abstract
+ * @return mixed
+ */
+ function getValue();
+
+ /**
+ * @abstract
+ * @return string
+ */
+ function getMetric();
+
+ /**
+ * @abstract
+ * @return string
+ */
+ function getMessage();
+
+ /**
+ * @abstract
+ * @return string
+ */
+ function __toString();
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Exception/InvalidArgumentException.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Exception/InvalidArgumentException.php
new file mode 100644
index 00000000..3f39d359
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Exception/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Liuggio\StatsdClient\Exception;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactory.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactory.php
new file mode 100644
index 00000000..7ec1a19e
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactory.php
@@ -0,0 +1,130 @@
+<?php
+
+namespace Liuggio\StatsdClient\Factory;
+
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+
+class StatsdDataFactory implements StatsdDataFactoryInterface
+{
+ /**
+ * @var StatsdDataInterface
+ */
+ private $entityClass;
+
+ public function __construct($entity_class = '\Liuggio\StatsdClient\Entity\StatsdData')
+ {
+ $this->setEntityClass($entity_class);
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function timing($key, $time)
+ {
+ return $this->produceStatsdData($key, $time, StatsdDataInterface::STATSD_METRIC_TIMING);
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function gauge($key, $value)
+ {
+ return $this->produceStatsdData($key, $value, StatsdDataInterface::STATSD_METRIC_GAUGE);
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function set($key, $value)
+ {
+ return $this->produceStatsdData($key, $value, StatsdDataInterface::STATSD_METRIC_SET);
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function increment($key)
+ {
+ return $this->produceStatsdData($key, 1, StatsdDataInterface::STATSD_METRIC_COUNT);
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function decrement($key)
+ {
+ return $this->produceStatsdData($key, -1, StatsdDataInterface::STATSD_METRIC_COUNT);
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function updateCount($key, $delta)
+ {
+ return $this->produceStatsdData($key, $delta, StatsdDataInterface::STATSD_METRIC_COUNT);
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function produceStatsdData($key, $value = 1, $metric = StatsdDataInterface::STATSD_METRIC_COUNT)
+ {
+ $statsdData = $this->produceStatsdDataEntity();
+
+ if (null !== $key) {
+ $statsdData->setKey($key);
+ }
+
+ if (null !== $value) {
+ $statsdData->setValue($value);
+ }
+
+ if (null !== $metric) {
+ $statsdData->setMetric($metric);
+ }
+
+ return $statsdData;
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function produceStatsdDataEntity()
+ {
+ $statsdData = $this->getEntityClass();
+
+ return new $statsdData();
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function setFailSilently($failSilently)
+ {
+ $this->failSilently = $failSilently;
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function getFailSilently()
+ {
+ return $this->failSilently;
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function setEntityClass($entityClass)
+ {
+ $this->entityClass = $entityClass;
+ }
+
+ /**
+ * {@inheritDoc}
+ **/
+ public function getEntityClass()
+ {
+ return $this->entityClass;
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactoryInterface.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactoryInterface.php
new file mode 100644
index 00000000..4d58833b
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Factory/StatsdDataFactoryInterface.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Liuggio\StatsdClient\Factory;
+
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+
+Interface StatsdDataFactoryInterface
+{
+
+ /**
+ * This function creates a 'timing' StatsdData.
+ *
+ * @abstract
+ *
+ * @param string|array $key The metric(s) to set.
+ * @param float $time The elapsed time (ms) to log
+ **/
+ function timing($key, $time);
+
+ /**
+ * This function creates a 'gauge' StatsdData.
+ *
+ * @abstract
+ *
+ * @param string|array $key The metric(s) to set.
+ * @param float $value The value for the stats.
+ **/
+ function gauge($key, $value);
+
+ /**
+ * This function creates a 'set' StatsdData object
+ * A "Set" is a count of unique events.
+ * This data type acts like a counter, but supports counting
+ * of unique occurrences of values between flushes. The backend
+ * receives the number of unique events that happened since
+ * the last flush.
+ *
+ * The reference use case involved tracking the number of active
+ * and logged in users by sending the current userId of a user
+ * with each request with a key of "uniques" (or similar).
+ *
+ * @abstract
+ *
+ * @param string|array $key The metric(s) to set.
+ * @param float $value The value for the stats.
+ *
+ * @return array
+ **/
+ function set($key, $value);
+
+ /**
+ * This function creates a 'increment' StatsdData object.
+ *
+ * @abstract
+ *
+ * @param string|array $key The metric(s) to increment.
+ * @param float|1 $sampleRate The rate (0-1) for sampling.
+ *
+ * @return array
+ **/
+ function increment($key);
+
+ /**
+ * This function creates a 'decrement' StatsdData object.
+ *
+ * @abstract
+ *
+ * @param string|array $key The metric(s) to decrement.
+ * @param float|1 $sampleRate The rate (0-1) for sampling.
+ *
+ * @return mixed
+ **/
+ function decrement($key);
+
+ /**
+ * This function creates a 'updateCount' StatsdData object.
+ *
+ * @abstract
+ *
+ * @param string|array $key The metric(s) to decrement.
+ * @param integer $delta The delta to add to the each metric
+ *
+ * @return mixed
+ **/
+ function updateCount($key, $delta);
+
+ /**
+ * Produce a StatsdDataInterface Object.
+ *
+ * @abstract
+ *
+ * @param string $key The key of the metric
+ * @param int $value The amount to increment/decrement each metric by.
+ * @param string $metric The metric type ("c" for count, "ms" for timing, "g" for gauge, "s" for set)
+ *
+ * @return StatsdDataInterface
+ **/
+ function produceStatsdData($key, $value = 1, $metric = StatsdDataInterface::STATSD_METRIC_COUNT);
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatter.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatter.php
new file mode 100644
index 00000000..6311112a
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatter.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace Liuggio\StatsdClient\Monolog\Formatter;
+
+use Monolog\Formatter\LineFormatter;
+
+/**
+ * Formats incoming records in order to be a perfect StatsD key.
+ *
+ * This is especially useful for logging to files
+ *
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @author Christophe Coevoet <stof@notk.org>
+ * @author Giulio De Donato <liuggio@gmail.com>
+ */
+class StatsDFormatter extends LineFormatter
+{
+ const SIMPLE_FORMAT = "%channel%.%level_name%.%short_message%";
+
+ protected $numberOfWords;
+ protected $logContext;
+ protected $logExtra;
+
+ /**
+ * @param string $format The format of the message
+ * @param Boolean $logContext If true add multiple rows containing Context information
+ * @param Boolean $logExtra If true add multiple rows containing Extra information
+ * @param integer $numberOfWords The number of words to show.
+ */
+ public function __construct($format = null, $logContext = true, $logExtra = true, $numberOfWords = 2)
+ {
+ $this->format = $format ? : static::SIMPLE_FORMAT;
+ $this->numberOfWords = $numberOfWords;
+ $this->logContext = $logContext;
+ $this->logExtra = $logExtra;
+ parent::__construct();
+ }
+
+ /**
+ * This function converts a long message into a string with the first N-words.
+ * eg. from: "Notified event "kernel.request" to listener "Symfony\Component\HttpKernel\EventListener"
+ * to: "Notified event"
+ *
+ * @param string $message The message to shortify.
+ *
+ * @return string
+ */
+ public function getFirstWords($message)
+ {
+ $glue = '-';
+ $pieces = explode(' ', $message);
+ array_splice($pieces, $this->numberOfWords);
+ $shortMessage = preg_replace("/[^A-Za-z0-9?![:space:]]/", "-", implode($glue, $pieces));
+
+ return $shortMessage;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function format(array $record)
+ {
+ $vars = $this->normalize($record);
+
+ $firstRow = $this->format;
+ $output = array();
+
+ $vars['short_message'] = $this->getFirstWords($vars['message']);
+ foreach ($vars as $var => $val) {
+ $firstRow = str_replace('%' . $var . '%', $this->convertToString($val), $firstRow);
+ }
+ $output[] = $firstRow;
+ // creating more rows for context content
+ if ($this->logContext && isset($vars['context'])) {
+ foreach ($vars['context'] as $key => $parameter) {
+ $output[] = sprintf("%s.context.%s.%s", $firstRow, $key, $parameter);
+ }
+ }
+ // creating more rows for extra content
+ if ($this->logExtra && isset($vars['extra'])) {
+ foreach ($vars['extra'] as $key => $parameter) {
+ $output[] = sprintf("%s.extra.%s.%s", $firstRow, $key, $parameter);
+ }
+ }
+
+ return $output;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formatBatch(array $records)
+ {
+ $output = array();
+ foreach ($records as $record) {
+ $output = array_merge($output, $this->format($record));
+ }
+
+ return $output;
+ }
+} \ No newline at end of file
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Handler/StatsDHandler.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Handler/StatsDHandler.php
new file mode 100644
index 00000000..86c6d399
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Monolog/Handler/StatsDHandler.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Liuggio\StatsdClient\Monolog\Handler;
+
+use Monolog\Logger;
+use Monolog\Handler\AbstractProcessingHandler;
+use Monolog\Formatter\FormatterInterface;
+
+use Liuggio\StatsdClient\Monolog\Formatter\StatsDFormatter;
+use Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface;
+use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+use Liuggio\StatsdClient\StatsdClientInterface;
+
+/**
+ * A processing handler for StatsD.
+ *
+ * @author Giulio De Donato <liuggio@gmail.com>
+ */
+class StatsDHandler extends AbstractProcessingHandler
+{
+ /**
+ * @var array
+ */
+ protected $buffer = array();
+
+ /**
+ * @var string
+ */
+ protected $prefix;
+
+ /**
+ * @var statsDService
+ */
+ protected $statsDService;
+
+ /**
+ * @var statsDFactory
+ */
+ protected $statsDFactory;
+
+ /**
+ * @param StatsdClientInterface $statsDService The Service sends the packet
+ * @param StatsdDataFactoryInterface $statsDFactory The Factory creates the StatsDPacket
+ * @param string $prefix Statsd key prefix
+ * @param integer $level The minimum logging level at which this handler will be triggered
+ * @param Boolean $bubble Whether the messages that are handled can bubble up the stack or not
+ */
+ public function __construct(StatsdClientInterface $statsDService, StatsdDataFactoryInterface $statsDFactory = null, $prefix, $level = Logger::DEBUG, $bubble = true)
+ {
+ parent::__construct($level, $bubble);
+
+ $this->statsDService = $statsDService;
+ $this->statsDFactory = $statsDFactory ? $statsDFactory : new StatsdDataFactory();
+ $this->prefix = $prefix;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ $this->statsDService->send($this->buffer);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function write(array $record)
+ {
+ $records = is_array($record['formatted']) ? $record['formatted'] : array($record['formatted']);
+
+ foreach ($records as $record) {
+ if (!empty($record)) {
+ $this->buffer[] = $this->statsDFactory->increment(sprintf('%s.%s', $this->getPrefix(), $record));
+ }
+ }
+ }
+
+ /**
+ * Gets the default formatter.
+ *
+ * @return FormatterInterface
+ */
+ protected function getDefaultFormatter()
+ {
+ return new StatsDFormatter();
+ }
+
+ /**
+ * @param string $prefix
+ */
+ public function setPrefix($prefix)
+ {
+ $this->prefix = $prefix;
+ }
+
+ /**
+ * @return string
+ */
+ public function getPrefix()
+ {
+ return $this->prefix;
+ }
+
+ /**
+ * @param StatsdClientInterface $statsDService
+ */
+ public function setStatsDService(StatsdClientInterface $statsDService)
+ {
+ $this->statsDService = $statsDService;
+ }
+
+ /**
+ * @param StatsdDataFactoryInterface $statsDFactory
+ */
+ public function setStatsDFactory(StatsdDataFactoryInterface $statsDFactory)
+ {
+ $this->statsDFactory = $statsDFactory;
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/EchoSender.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/EchoSender.php
new file mode 100644
index 00000000..1015ca94
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/EchoSender.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Liuggio\StatsdClient\Sender;
+
+
+Class EchoSender implements SenderInterface
+{
+ /**
+ * {@inheritDoc}
+ */
+ public function open()
+ {
+ echo "[open]";
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ function write($handle, $message, $length = null)
+ {
+ echo "[$message]";
+
+ return strlen($message);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ function close($handle)
+ {
+ echo "[closed]";
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SenderInterface.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SenderInterface.php
new file mode 100644
index 00000000..8dc8fd3a
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SenderInterface.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Liuggio\StatsdClient\Sender;
+
+Interface SenderInterface
+{
+ /**
+ * @abstract
+ * @return mixed
+ */
+ function open();
+
+ /**
+ * @abstract
+ *
+ * @param $handle
+ * @param string $string
+ * @param null $length
+ *
+ * @return mixed
+ */
+ function write($handle, $string, $length = null);
+
+ /**
+ * @abstract
+ *
+ * @param $handle
+ *
+ * @return mixed
+ */
+ function close($handle);
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SocketSender.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SocketSender.php
new file mode 100644
index 00000000..c319f077
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SocketSender.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Liuggio\StatsdClient\Sender;
+
+use Liuggio\StatsdClient\Exception\InvalidArgumentException;
+
+Class SocketSender implements SenderInterface
+{
+ private $port;
+ private $host;
+ private $protocol;
+
+ public function __construct($hostname = 'localhost', $port = 8126, $protocol = 'udp')
+ {
+ $this->host = $hostname;
+ $this->port = $port;
+
+ switch ($protocol) {
+ case 'udp':
+ $this->protocol = SOL_UDP;
+ break;
+ case 'tcp':
+ $this->protocol = SOL_TCP;
+ break;
+ default:
+ throw new InvalidArgumentException(sprintf('Use udp or tcp as protocol given %s', $protocol));
+ break;
+ }
+
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function open()
+ {
+ $fp = socket_create(AF_INET, SOCK_DGRAM, $this->getProtocol());
+
+ return $fp;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function write($handle, $message, $length = null)
+ {
+ return socket_sendto($handle, $message, strlen($message), 0, $this->getHost(), $this->getPort());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function close($handle)
+ {
+ socket_close($handle);
+ }
+
+
+ protected function setHost($host)
+ {
+ $this->host = $host;
+ }
+
+ protected function getHost()
+ {
+ return $this->host;
+ }
+
+ protected function setPort($port)
+ {
+ $this->port = $port;
+ }
+
+ protected function getPort()
+ {
+ return $this->port;
+ }
+
+ protected function setProtocol($protocol)
+ {
+ $this->protocol = $protocol;
+ }
+
+ protected function getProtocol()
+ {
+ return $this->protocol;
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SysLogSender.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SysLogSender.php
new file mode 100644
index 00000000..537ead39
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/Sender/SysLogSender.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Liuggio\StatsdClient\Sender;
+
+
+Class SysLogSender implements SenderInterface
+{
+ private $priority;
+
+ public function __construct($priority = LOG_INFO)
+ {
+ $this->priority = $priority;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function open()
+ {
+ syslog($this->priority, "statsd-client-open");
+
+ return true;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ function write($handle, $message, $length = null)
+ {
+ syslog($this->priority, sprintf("statsd-client-write \"%s\" %d Bytes", $message, strlen($message)));
+
+ return strlen($message);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ function close($handle)
+ {
+ syslog($this->priority, "statsd-client-close");
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClient.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClient.php
new file mode 100644
index 00000000..a1d232a5
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClient.php
@@ -0,0 +1,211 @@
+<?php
+
+namespace Liuggio\StatsdClient;
+
+use Liuggio\StatsdClient\Sender\SenderInterface;
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+use Liuggio\StatsdClient\Exception\InvalidArgumentException;
+
+class StatsdClient implements StatsdClientInterface
+{
+ /**
+ * @var boolean
+ */
+ private $failSilently;
+
+ /**
+ * @var \Liuggio\StatsdClient\Sender\SenderInterface
+ */
+ private $sender;
+
+ /**
+ * @var boolean
+ */
+ private $reducePacket;
+
+ /**
+ * Constructor.
+ *
+ * @param \Liuggio\StatsdClient\Sender\SenderInterface $sender
+ * @param Boolean $reducePacket
+ * @param Boolean $fail_silently
+ */
+ public function __construct(SenderInterface $sender, $reducePacket = true, $fail_silently = true)
+ {
+ $this->sender = $sender;
+ $this->reducePacket = $reducePacket;
+ $this->failSilently = $fail_silently;
+ }
+
+ /**
+ * Throws an exc only if failSilently if getFailSilently is false.
+ *
+ * @param \Exception $exception
+ *
+ * @throws \Exception
+ */
+ private function throwException(\Exception $exception)
+ {
+ if (!$this->getFailSilently()) {
+ throw $exception;
+ }
+ }
+
+ /**
+ * This function reduces the number of packets,the reduced has the maximum dimension of self::MAX_UDP_SIZE_STR
+ * Reference:
+ * https://github.com/etsy/statsd/blob/master/README.md
+ * All metrics can also be batch send in a single UDP packet, separated by a newline character.
+ *
+ * @param array $reducedMetrics
+ * @param array $metric
+ *
+ * @return array
+ */
+ private static function doReduce($reducedMetrics, $metric)
+ {
+ $metricLength = strlen($metric);
+ $lastReducedMetric = count($reducedMetrics) > 0 ? end($reducedMetrics) : null;
+
+ if ($metricLength >= self::MAX_UDP_SIZE_STR
+ || null === $lastReducedMetric
+ || strlen($newMetric = $lastReducedMetric . "\n" . $metric) > self::MAX_UDP_SIZE_STR
+ ) {
+ $reducedMetrics[] = $metric;
+ } else {
+ array_pop($reducedMetrics);
+ $reducedMetrics[] = $newMetric;
+ }
+
+ return $reducedMetrics;
+ }
+
+
+ /**
+ * this function reduce the amount of data that should be send
+ *
+ * @param mixed $arrayData
+ *
+ * @return mixed $arrayData
+ */
+ public function reduceCount($arrayData)
+ {
+ if (is_array($arrayData)) {
+ $arrayData = array_reduce($arrayData, "self::doReduce", array());
+ }
+
+ return $arrayData;
+ }
+
+ /**
+ * Reference: https://github.com/etsy/statsd/blob/master/README.md
+ * Sampling 0.1
+ * Tells StatsD that this counter is being sent sampled every 1/10th of the time.
+ *
+ * @param mixed $data
+ * @param int $sampleRate
+ *
+ * @return mixed $data
+ */
+ public function appendSampleRate($data, $sampleRate = 1)
+ {
+ $sampledData = array();
+ if ($sampleRate < 1) {
+ foreach ($data as $key => $message) {
+ $sampledData[$key] = sprintf('%s|@%s', $message, $sampleRate);
+ }
+ $data = $sampledData;
+ }
+
+ return $data;
+ }
+
+ /*
+ * Send the metrics over UDP
+ *
+ * {@inheritDoc}
+ */
+ public function send($data, $sampleRate = 1)
+ {
+ // check format
+ if ($data instanceof StatsdDataInterface || is_string($data)) {
+ $data = array($data);
+ }
+ if (!is_array($data) || empty($data)) {
+ return;
+ }
+ // add sampling
+ if ($sampleRate < 1) {
+ $data = $this->appendSampleRate($data, $sampleRate);
+ }
+ // reduce number of packets
+ if ($this->getReducePacket()) {
+ $data = $this->reduceCount($data);
+ }
+ //failures in any of this should be silently ignored if ..
+ try {
+ $fp = $this->getSender()->open();
+ if (!$fp) {
+ return;
+ }
+ $written = 0;
+ foreach ($data as $key => $message) {
+ $written += $this->getSender()->write($fp, $message);
+ }
+ $this->getSender()->close($fp);
+ } catch (\Exception $e) {
+ $this->throwException($e);
+ }
+
+ return $written;
+ }
+
+ /**
+ * @param boolean $failSilently
+ */
+ public function setFailSilently($failSilently)
+ {
+ $this->failSilently = $failSilently;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getFailSilently()
+ {
+ return $this->failSilently;
+ }
+
+ /**
+ * @param \Liuggio\StatsdClient\Sender\SenderInterface $sender
+ */
+ public function setSender(SenderInterface $sender)
+ {
+ $this->sender = $sender;
+ }
+
+ /**
+ * @return \Liuggio\StatsdClient\Sender\SenderInterface
+ */
+ public function getSender()
+ {
+ return $this->sender;
+ }
+
+ /**
+ * @param boolean $reducePacket
+ */
+ public function setReducePacket($reducePacket)
+ {
+ $this->reducePacket = $reducePacket;
+ }
+
+ /**
+ * @return boolean
+ */
+ public function getReducePacket()
+ {
+ return $this->reducePacket;
+ }
+
+}
diff --git a/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClientInterface.php b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClientInterface.php
new file mode 100644
index 00000000..e2c75cda
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/src/Liuggio/StatsdClient/StatsdClientInterface.php
@@ -0,0 +1,23 @@
+<?php
+
+namespace Liuggio\StatsdClient;
+
+use Liuggio\StatsdClient\Sender\SenderInterface;
+use Liuggio\StatsdClient\Entity\StatsdDataInterface;
+use Liuggio\StatsdClient\Exception\InvalidArgumentException;
+
+Interface StatsdClientInterface
+{
+ const MAX_UDP_SIZE_STR = 512;
+
+ /*
+ * Send the metrics over UDP
+ *
+ * @abstract
+ * @param array|string|StatsdDataInterface $data message(s) to sent
+ * @param int $sampleRate Tells StatsD that this counter is being sent sampled every Xth of the time.
+ *
+ * @return integer the data sent in bytes
+ */
+ function send($data, $sampleRate = 1);
+}
diff --git a/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Entity/StatsdDataTest.php b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Entity/StatsdDataTest.php
new file mode 100644
index 00000000..fad9175c
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Entity/StatsdDataTest.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Liuggio\StatsdClient\Entity;
+
+use Liuggio\StatsdClient\Entity\StatsdData;
+
+
+class StatsdDataTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGetMessage()
+ {
+ $statsdData = new StatsdData();
+ $statsdData->setKey('key');
+ $statsdData->setValue('value');
+ $statsdData->setMetric('c');
+
+ $this->assertEquals($statsdData->getMessage(), 'key:value|c');
+
+ $statsdData = new StatsdData();
+ $statsdData->setKey('key');
+ $statsdData->setValue(-1);
+ $statsdData->setMetric('c');
+
+ $this->assertEquals($statsdData->getMessage(), 'key:-1|c');
+
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatterTest.php b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatterTest.php
new file mode 100644
index 00000000..daf899aa
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Formatter/StatsDFormatterTest.php
@@ -0,0 +1,169 @@
+<?php
+
+namespace Liuggio\StatsdClient\Tests\Monolog\Formatter;
+
+use Monolog\Logger;
+use Liuggio\StatsdClient\Monolog\Formatter\StatsDFormatter;
+
+/**
+ * @covers Liuggio\StatsdClient\Monolog\Formatter\StatsDFormatter
+ */
+class StatsDFormatterTest extends \PHPUnit_Framework_TestCase
+{
+ public function testBatchFormat()
+ {
+ $formatter = new StatsDFormatter(null, 2);
+ $message = $formatter->formatBatch(array(
+ array(
+ 'level_name' => 'CRITICAL',
+ 'channel' => 'test',
+ 'message' => 'bar',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ),
+ array(
+ 'level_name' => 'WARNING',
+ 'channel' => 'log',
+ 'message' => 'foo',
+ 'context' => array(),
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ),
+ ));
+
+ $this->assertEquals(array('test.CRITICAL.bar', 'log.WARNING.foo'), $message);
+ }
+
+ public function testDefFormatWithString()
+ {
+ $formatter = new StatsDFormatter(StatsDFormatter::SIMPLE_FORMAT);
+ $message = $formatter->format(array(
+ 'level_name' => 'WARNING',
+ 'channel' => 'log',
+ 'context' => array(),
+ 'message' => 'foo',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ));
+ $this->assertEquals(array('log.WARNING.foo'), $message);
+ }
+
+ public function testDefFormatWithArrayContext()
+ {
+ $formatter = new StatsDFormatter();
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'message' => 'foo',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ )
+ ));
+
+ $assert = array('meh.ERROR.foo',
+ 'meh.ERROR.foo.context.foo.bar',
+ 'meh.ERROR.foo.context.baz.qux');
+
+ $this->assertEquals($assert, $message);
+ }
+
+ public function testDefFormatWithArrayContextAndExtra()
+ {
+ $formatter = new StatsDFormatter();
+ $message = $formatter->format(array(
+ 'level_name' => 'ERROR',
+ 'channel' => 'meh',
+ 'message' => 'foo',
+ 'datetime' => new \DateTime,
+ 'extra' => array('extra'=>'woow'),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ )
+ ));
+
+ $assert = array('meh.ERROR.foo',
+ 'meh.ERROR.foo.context.foo.bar',
+ 'meh.ERROR.foo.context.baz.qux',
+ 'meh.ERROR.foo.extra.extra.woow');
+
+ $this->assertEquals($assert, $message);
+
+ }
+
+ public function testDefLongFormat()
+ {
+ $formatter = new StatsDFormatter();
+ $message = $formatter->format(array(
+ 'level_name' => 'DEBUG',
+ 'channel' => 'doctrine',
+ 'message' => 'INSERT INTO viaggio_calendar (enable, viaggio_id, calendar_id) VALUES (?, ?, ?)',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ )
+ ));
+ $this->assertEquals(array("doctrine.DEBUG.INSERT-INTO",
+ "doctrine.DEBUG.INSERT-INTO.context.foo.bar",
+ "doctrine.DEBUG.INSERT-INTO.context.baz.qux"), $message);
+ }
+
+ public function testDefLongFormatWith3WordsNoContextAndNoExtra()
+ {
+ $formatter = new StatsDFormatter(null, false, false, 3);
+ $message = $formatter->format(array(
+ 'level_name' => 'DEBUG',
+ 'channel' => 'doctrine',
+ 'message' => 'INSERT INTO viaggio_calendar (enable, viaggio_id, calendar_id) VALUES (?, ?, ?)',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ )
+ ));
+ $this->assertEquals(array("doctrine.DEBUG.INSERT-INTO-viaggio-calendar"), $message);
+ }
+ public function testDefRouteException()
+ {
+ $formatter = new StatsDFormatter();
+ $message = $formatter->format(array(
+ 'level_name' => 'DEBUG',
+ 'channel' => 'doctrine',
+ 'message' => 'Symfony\Component\HttpKernel\Exception\NotFoundHttpException: No route found for "GET /ddd" (uncaught exception) at /xxxx/classes.php line 5062',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ ));
+ $this->assertEquals(array('doctrine.DEBUG.Symfony-Component-HttpKernel-Exception-NotFoundHttpException--No'), $message);
+ }
+
+ public function testDefKernelException()
+ {
+ $formatter = new StatsDFormatter();
+ $message = $formatter->format(array(
+ 'level_name' => 'DEBUG',
+ 'channel' => 'doctrine',
+ 'message' => 'Notified event "kernel.exception" to listener "Symfony\Component\HttpKernel\EventListener\ProfilerListener::onKernelException"',
+ 'datetime' => new \DateTime,
+ 'extra' => array(),
+ 'context' => array(
+ 'foo' => 'bar',
+ 'baz' => 'qux',
+ )
+ ));
+
+ $assert = array('doctrine.DEBUG.Notified-event',
+ 'doctrine.DEBUG.Notified-event.context.foo.bar',
+ 'doctrine.DEBUG.Notified-event.context.baz.qux');
+
+ $this->assertEquals($assert, $message);
+
+
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Handler/StatsDHandlerTest.php b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Handler/StatsDHandlerTest.php
new file mode 100644
index 00000000..c1b8eb4b
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/Monolog/Handler/StatsDHandlerTest.php
@@ -0,0 +1,89 @@
+<?php
+
+namespace Liuggio\StatsdClient\Tests\Monolog\Handler;
+
+use Monolog\Logger;
+use Liuggio\StatsdClient\Monolog\Handler\StatsDHandler;
+
+
+class StatsDHandlerTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @return array Record
+ */
+ protected function getRecord($level = Logger::WARNING, $message = 'test', $context = array())
+ {
+ return array(
+ 'message' => $message,
+ 'context' => $context,
+ 'level' => $level,
+ 'level_name' => Logger::getLevelName($level),
+ 'channel' => 'test',
+ 'datetime' => \DateTime::createFromFormat('U.u', sprintf('%.6F', microtime(true))),
+ 'extra' => array(),
+ );
+ }
+
+ /**
+ * @return array
+ */
+ protected function getMultipleRecords()
+ {
+ return array(
+ $this->getRecord(Logger::DEBUG, 'debug message 1'),
+ $this->getRecord(Logger::DEBUG, 'debug message 2'),
+ $this->getRecord(Logger::INFO, 'information'),
+ $this->getRecord(Logger::WARNING, 'warning'),
+ $this->getRecord(Logger::ERROR, 'error')
+ );
+ }
+
+ /**
+ * @return Monolog\Formatter\FormatterInterface
+ */
+ protected function getIdentityFormatter()
+ {
+ $formatter = $this->getMock('Monolog\\Formatter\\FormatterInterface');
+ $formatter->expects($this->any())
+ ->method('format')
+ ->will($this->returnCallback(function($record) { return $record['message']; }));
+
+ return $formatter;
+ }
+
+
+ protected function setup()
+ {
+ if (!interface_exists('Liuggio\StatsdClient\StatsdClientInterface')) {
+ $this->markTestSkipped('The "liuggio/statsd-php-client" package is not installed');
+ }
+ }
+
+ public function testHandle()
+ {
+ $client = $this->getMock('Liuggio\StatsdClient\StatsdClientInterface');
+ $factory = $this->getMock('Liuggio\StatsdClient\Factory\StatsdDataFactoryInterface');
+
+ $factory->expects($this->any())
+ ->method('increment')
+ ->will($this->returnCallback(function ($input){
+ return sprintf('%s|c|1', $input);
+ }));
+
+ $prefixToAssert = 'prefix';
+ $messageToAssert = 'test-msg';
+
+ $record = $this->getRecord(Logger::WARNING, $messageToAssert, array('data' => new \stdClass, 'foo' => 34));
+
+ $assert = array(sprintf('%s.test.WARNING.%s|c|1',$prefixToAssert, $messageToAssert),
+ sprintf('%s.test.WARNING.%s.context.data.[object] (stdClass: {})|c|1',$prefixToAssert, $messageToAssert),
+ sprintf('%s.test.WARNING.%s.context.foo.34|c|1',$prefixToAssert, $messageToAssert));
+
+ $client->expects($this->once())
+ ->method('send')
+ ->with($assert);
+
+ $handler = new StatsDHandler($client, $factory, $prefixToAssert);
+ $handler->handle($record);
+ }
+} \ No newline at end of file
diff --git a/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/ReadmeTest.php b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/ReadmeTest.php
new file mode 100644
index 00000000..4608a601
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/ReadmeTest.php
@@ -0,0 +1,72 @@
+<?php
+
+namespace Liuggio\StatsdClient;
+
+use Liuggio\StatsdClient\StatsdClient;
+use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+//use Liuggio\StatsdClient\Sender\SocketSender;
+
+
+class ReadmeTest extends \PHPUnit_Framework_TestCase
+{
+ public function testFullUsageWithObject() {
+
+ $sender = $this->mockSender();
+ // $sender = new Sender();
+
+ // StatsdClient(SenderInterface $sender, $host = 'udp://localhost', $port = 8126, $reducePacket = true, $fail_silently = true)
+ $client = new StatsdClient($sender);
+ $factory = new StatsdDataFactory('\Liuggio\StatsdClient\Entity\StatsdData');
+
+ // create the data with the factory
+ $data[] = $factory->timing('usageTime', 100);
+ $data[] = $factory->increment('visitor');
+ $data[] = $factory->decrement('click');
+ $data[] = $factory->gauge('gaugor', 333);
+ $data[] = $factory->set('uniques', 765);
+
+ // send the data as array or directly as object
+ $client->send($data);
+ }
+
+
+
+ public function testFullUsageArray() {
+
+ $sender = $this->mockSender();
+ // $sender = new Sender();
+
+ // StatsdClient(SenderInterface $sender, $host = 'localhost', $port = 8126, $protocol='udp', $reducePacket = true, $fail_silently = true)
+ $client = new StatsdClient($sender, $host = 'localhost', $port = 8126, 'udp', $reducePacket = true, $fail_silently = true);
+
+ $data[] ="increment:1|c";
+ $data[] ="set:value|s";
+ $data[] ="gauge:value|g";
+ $data[] = "timing:10|ms";
+ $data[] = "decrement:-1|c";
+ $data[] ="key:1|c";
+
+ // send the data as array or directly as object
+ $client->send($data);
+ }
+
+
+ private function mockSender() {
+ $sender = $this->getMock('\Liuggio\StatsdClient\Sender\SenderInterface', array('open', 'write', 'close'));
+ $sender->expects($this->once())
+ ->method('open')
+ ->will($this->returnValue(true));
+
+ $sender->expects($this->any()) //If you set the reduce = true into the StatsdClient the write will be called once
+ ->method('write')
+ ->will($this->returnCallBack(function($fp, $message) {
+ // echo PHP_EOL . "- " . $message;
+ }));
+
+ $sender->expects($this->once())
+ ->method('close')
+ ->will($this->returnValue(true));
+
+ return $sender;
+ }
+} \ No newline at end of file
diff --git a/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdClientTest.php b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdClientTest.php
new file mode 100644
index 00000000..865f1b2d
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdClientTest.php
@@ -0,0 +1,228 @@
+<?php
+
+namespace Liuggio\StatsdClient;
+
+use Liuggio\StatsdClient\StatsdClient;
+use Liuggio\StatsdClient\Entity\StatsdData;
+
+class StatsdClientTest extends \PHPUnit_Framework_TestCase
+{
+
+ public function mockSenderWithAssertionOnWrite($messageToAssert=null) {
+
+ $mock = $this->getMockBuilder('\Liuggio\StatsdClient\Sender\SocketSender') ->disableOriginalConstructor() ->getMock();
+
+ $phpUnit = $this;
+ $mock->expects($this->any())
+ ->method('open')
+ ->will($this->returnValue(true));
+ // if the input is an array expects a call foreach item
+ if (is_array($messageToAssert)) {
+ $index = 0;
+ foreach ($messageToAssert as $oneMessage) {
+ $index++;
+ $mock->expects($this->at($index))
+ ->method('write')
+ ->will($this->returnCallBack(function($fp, $message) use ($phpUnit, $oneMessage) {
+ $phpUnit->assertEquals($message, $oneMessage);
+ }));
+ }
+ } else if (null !== $messageToAssert){
+ // if the input is a string expects only once
+ $mock->expects($this->once())
+ ->method('write')
+ ->will($this->returnCallBack(function($fp, $message) use ($phpUnit, $messageToAssert) {
+ $phpUnit->assertEquals($message, $messageToAssert);
+ }));
+ }
+ return $mock;
+ }
+
+ public function mockStatsdClientWithAssertionOnWrite($messageToAssert) {
+
+ $mockSender = $this->mockSenderWithAssertionOnWrite($messageToAssert);
+
+ return new StatsdClient($mockSender, false, false);
+ }
+
+ public function mockFactory() {
+
+ $mock = $this->getMock('\Liuggio\StatsdClient\Factory\StatsdDataFactory', array('timing'));
+
+ $statsData = new StatsdData();
+ $statsData->setKey('key');
+ $statsData->setValue('1');
+ $statsData->setMetric('ms');
+
+ $phpUnit = $this;
+ $mock->expects($this->any())
+ ->method('timing')
+ ->will($this->returnValue($statsData));
+
+ return $mock;
+ }
+
+ public static function provider()
+ {
+ /**
+ * First
+ */
+ $statsData0 = new StatsdData();
+ $statsData0->setKey('keyTiming');
+ $statsData0->setValue('1');
+ $statsData0->setMetric('ms');
+ /**
+ * Second
+ */
+ $stats1 = array();
+ $statsData1 = new StatsdData();
+ $statsData1->setKey('keyTiming');
+ $statsData1->setValue('1');
+ $statsData1->setMetric('ms');
+ $stats1[] = $statsData1;
+
+ $statsData1 = new StatsdData();
+ $statsData1->setKey('keyIncrement');
+ $statsData1->setValue('1');
+ $statsData1->setMetric('c');
+ $stats1[] = $statsData1;
+
+ return array(
+ array($statsData0, "keyTiming:1|ms"),
+ array($stats1, array("keyTiming:1|ms", "keyIncrement:1|c")),
+ );
+ }
+ public static function providerSend()
+ {
+ return array(
+ array(array('gauge:value|g'), 'gauge:value|g'),
+ array(array("keyTiming:1|ms", "keyIncrement:1|c"), array("keyTiming:1|ms", "keyIncrement:1|c")),
+ );
+ }
+
+ /**
+ * @dataProvider provider
+ */
+ public function testPrepareAndSend($statsdInput, $assertion) {
+
+ $statsdMock = $this->mockStatsdClientWithAssertionOnWrite($assertion);
+ $statsdMock->send($statsdInput);
+ }
+
+ /**
+ * @dataProvider providerSend
+ */
+ public function testSend($array, $assertion) {
+
+ $statsdMock = $this->mockStatsdClientWithAssertionOnWrite($assertion);
+ $statsdMock->send($array);
+ }
+
+ public function testReduceCount()
+ {
+ $statsd = $this->mockStatsdClientWithAssertionOnWrite(null);
+
+ $entity0 = new StatsdData();
+ $entity0->setKey('key1');
+ $entity0->setValue('1');
+ $entity0->setMetric('c');
+ $array0[] = $entity0;
+
+ $entity0 = new StatsdData();
+ $entity0->setKey('key2');
+ $entity0->setValue('2');
+ $entity0->setMetric('ms');
+ $array0[] = $entity0;
+
+ $reducedMessage = array('key1:1|c' . PHP_EOL . 'key2:2|ms');
+
+ $this->assertEquals($statsd->reduceCount($array0), $reducedMessage);
+
+ }
+
+ public function testReduceWithString()
+ {
+ $statsd = $this->mockStatsdClientWithAssertionOnWrite(null);
+
+ $msg = 'A3456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789:';
+ $msg .= '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789|c';
+ $array0[] = $msg;
+
+ $msg = 'B3456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789:';
+ $msg .= '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789|c';
+ $array0[] = $msg;
+ $reduced = $statsd->reduceCount($array0);
+ $combined = $array0[0] . PHP_EOL . $array0[1];
+ $this->assertEquals($combined, $reduced[0]);
+ }
+
+
+ public function testReduceWithMaxUdpPacketSplitInTwoPacket()
+ {
+ $statsd = $this->mockStatsdClientWithAssertionOnWrite(null);
+
+ $msg = 'A3456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789'; //1
+ $msg .= '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 '; //2
+ $msg .= '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 '; //3
+ $msg .= '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 '; //4
+ $msg .= '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789|c'; //500
+ $array0[] = $msg;
+
+ $msg = 'Bkey:';
+ $msg .= '123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789|c';
+ $array0[] = $msg;
+
+ $reduced = $statsd->reduceCount($array0);
+
+ $this->assertEquals($array0[0], $reduced[0]);
+ $this->assertEquals($array0[1], $reduced[1]);
+ }
+
+
+
+ public function testMultiplePacketsWithReducing()
+ {
+
+ $msg = 'A23456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789 123456789';
+ $array0[] = $msg;
+ $array0[] = $msg;
+ $array0[] = $msg;
+ $array0[] = $msg;
+ $array0[] = $msg;
+ $array0[] = $msg;
+ $array0[] = $msg;
+ $array0[] = $msg;
+
+ $total = count($array0) * strlen($msg);
+
+ $reducedPacketsAssertion = (int) ceil($total / StatsdClientInterface::MAX_UDP_SIZE_STR);
+
+
+ $mockSender = $this->mockSenderWithAssertionOnWrite();
+ $statsd = new StatsdClient($mockSender, true, false);
+ $reduced = $statsd->reduceCount($array0);
+
+ $this->assertEquals($reducedPacketsAssertion, count($reduced));
+ }
+
+ public function testSampleRate()
+ {
+ $senderMock = $this->getMock('Liuggio\StatsdClient\Sender\SenderInterface');
+ $senderMock
+ ->expects($this->once())
+ ->method('open')
+ ->will($this->returnValue(true))
+ ;
+ $senderMock
+ ->expects($this->once())
+ ->method('write')
+ ->with($this->anything(), 'foo|@0.2')
+ ;
+ $client = new StatsdClient($senderMock, false, false);
+
+ $client->send(
+ 'foo',
+ 0.2
+ );
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdDataFactoryTest.php b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdDataFactoryTest.php
new file mode 100644
index 00000000..144f629c
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/tests/Liuggio/StatsdClient/StatsdDataFactoryTest.php
@@ -0,0 +1,97 @@
+<?php
+
+namespace Liuggio\StatsdClient\Factory;
+
+use Liuggio\StatsdClient\Factory\StatsdDataFactory;
+
+class StatsDataFactoryTest extends \PHPUnit_Framework_TestCase
+{
+ private $statsDataFactory;
+
+ public function setUp()
+ {
+ $this->statsDataFactory = new StatsdDataFactory('\Liuggio\StatsdClient\Entity\StatsdData');
+ }
+
+ public function testProduceStatsdData()
+ {
+ $key = 'key';
+ $value='val';
+
+ $obj = $this->statsDataFactory->produceStatsdData($key, $value);
+ $this->assertEquals($key, $obj->getKey());
+ $this->assertEquals($value, $obj->getValue());
+ }
+
+ public function testTiming()
+ {
+ $key = 'key';
+ $value = microtime();
+ $valueFloat = (string) floatval($value);
+
+ $obj = $this->statsDataFactory->timing($key, $value);
+ $this->assertEquals($key, $obj->getKey());
+ $this->assertContains($valueFloat, $obj->getValue());
+ $this->assertContains('ms', $obj->getMetric());
+ }
+
+ public function testProduceStatsdDataDecrement()
+ {
+ $key = 'key';
+ $value = -1;
+ $stringValue = intval($value);
+
+ $obj = $this->statsDataFactory->produceStatsdData($key, $value);
+ $this->assertEquals($key, $obj->getKey());
+ $this->assertEquals($stringValue, $obj->getValue());
+ $this->assertEquals('c', $obj->getMetric());
+ }
+
+ public function testGauge()
+ {
+ $key = 'key';
+ $value = 1000;
+ $stringValue = (string) intval($value);
+
+ $obj = $this->statsDataFactory->gauge($key, $value);
+ $this->assertEquals($key, $obj->getKey());
+ $this->assertEquals($stringValue, $obj->getValue());
+ $this->assertEquals('g', $obj->getMetric());
+ }
+
+ public function testDecrement()
+ {
+ $key = 'key';
+ $value = -1;
+ $stringValue = intval($value);
+
+ $obj = $this->statsDataFactory->decrement($key);
+ $this->assertEquals($key, $obj->getKey());
+ $this->assertEquals($stringValue, $obj->getValue());
+ $this->assertEquals('c', $obj->getMetric());
+ }
+
+ public function testcreateStatsdDataIncrement()
+ {
+ $key = 'key';
+ $value = 1;
+ $stringValue = intval($value);
+
+ $obj = $this->statsDataFactory->increment($key);
+ $this->assertEquals($key, $obj->getKey());
+ $this->assertEquals($stringValue, $obj->getValue());
+ $this->assertEquals('c', $obj->getMetric());
+ }
+
+ public function testCreateStatsdDataUpdateCount()
+ {
+ $key = 'key';
+ $value = 10;
+ $stringValue = intval($value);
+
+ $obj = $this->statsDataFactory->updateCount($key, 10);
+ $this->assertEquals($key, $obj->getKey());
+ $this->assertEquals($stringValue, $obj->getValue());
+ $this->assertEquals('c', $obj->getMetric());
+ }
+}
diff --git a/vendor/liuggio/statsd-php-client/tests/bootstrap.php b/vendor/liuggio/statsd-php-client/tests/bootstrap.php
new file mode 100644
index 00000000..0594a467
--- /dev/null
+++ b/vendor/liuggio/statsd-php-client/tests/bootstrap.php
@@ -0,0 +1,5 @@
+<?php
+
+
+$loader = require_once __DIR__ . "/../vendor/autoload.php";
+$loader->add('Liuggio\\', __DIR__); \ No newline at end of file
diff --git a/vendor/oojs/oojs-ui/.csscomb.json b/vendor/oojs/oojs-ui/.csscomb.json
new file mode 100644
index 00000000..c4e215ec
--- /dev/null
+++ b/vendor/oojs/oojs-ui/.csscomb.json
@@ -0,0 +1,24 @@
+{
+ "remove-empty-rulesets": true,
+ "always-semicolon": true,
+ "color-case": "lower",
+ "block-indent": "\t",
+ "color-shorthand": false,
+ "element-case": "lower",
+ "eof-newline": true,
+ "leading-zero": true,
+ "quotes": "double",
+ "space-before-colon": "",
+ "space-after-colon": " ",
+ "space-before-combinator": " ",
+ "space-after-combinator": " ",
+ "space-between-declarations": "\n",
+ "space-before-opening-brace": " ",
+ "space-after-opening-brace": "\n",
+ "space-after-selector-delimiter": "\n",
+ "space-before-selector-delimiter": "",
+ "space-before-closing-brace": "\n",
+ "strip-spaces": true,
+ "unitless-zero": true,
+ "vendor-prefix-align": true
+}
diff --git a/vendor/oojs/oojs-ui/.csslintrc b/vendor/oojs/oojs-ui/.csslintrc
new file mode 100644
index 00000000..e777c7f3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/.csslintrc
@@ -0,0 +1,11 @@
+{
+ "adjoining-classes": false,
+ "box-model": false,
+ "box-sizing": false,
+ "fallback-colors": false,
+ "important": false,
+ "outline-none": false,
+ "qualified-headings": false,
+ "universal-selector": false,
+ "unqualified-attributes": false
+}
diff --git a/vendor/oojs/oojs-ui/.mailmap b/vendor/oojs/oojs-ui/.mailmap
new file mode 100644
index 00000000..84db0c6e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/.mailmap
@@ -0,0 +1,18 @@
+Alex Monk <krenair@gmail.com>
+Alex Monk <krenair@gmail.com> <krenair@wikimedia.org>
+Bartosz Dziewoński <matma.rex@gmail.com>
+Christian Williams <christian@wikia-inc.com>
+David Chan <david@sheetmusic.org.uk>
+Ed Sanders <esanders@wikimedia.org>
+Inez Korczynski <inez@wikia-inc.com>
+James D. Forrester <jforrester@wikimedia.org>
+Kirsten Menger-Anderson <kmenger@wikimedia.org>
+Marielle Volz <marielle.volz@gmail.com>
+Moriel Schottlender <moriel@gmail.com>
+Roan Kattouw <roan.kattouw@gmail.com>
+Roan Kattouw <roan.kattouw@gmail.com> <roan@wikimedia.org>
+Rob Moen <rmoen@wikimedia.org>
+Sucheta Ghoshal <sghoshal@wikimedia.org>
+Timo Tijhof <krinklemail@gmail.com>
+Trevor Parscal <trevorparscal@gmail.com>
+
diff --git a/vendor/oojs/oojs-ui/.npmignore b/vendor/oojs/oojs-ui/.npmignore
new file mode 100644
index 00000000..67aebbfc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/.npmignore
@@ -0,0 +1,2 @@
+/docs
+vendor
diff --git a/vendor/oojs/oojs-ui/AUTHORS.txt b/vendor/oojs/oojs-ui/AUTHORS.txt
new file mode 100644
index 00000000..6813d169
--- /dev/null
+++ b/vendor/oojs/oojs-ui/AUTHORS.txt
@@ -0,0 +1,34 @@
+Principal Authors (major contributors, alphabetically)
+
+Bartosz Dziewoński <matma.rex@gmail.com>
+Ed Sanders <esanders@wikimedia.org>
+James D. Forrester <jforrester@wikimedia.org>
+Kirsten Menger-Anderson <kmenger@wikimedia.org>
+Roan Kattouw <roan@wikimedia.org>
+Rob Moen <rmoen@wikimedia.org>
+Timo Tijhof <timo@wikimedia.org>
+Trevor Parscal <trevor@wikimedia.org>
+
+Patch Contributors (minor contributors, alphabetically)
+
+Alex Monk <krenair@wikimedia.org>
+Amir E. Aharoni <aaharoni@wikimedia.org>
+Antoine Musso <hashar@free.fr>
+Derk-Jan Hartman <hartman.wiki@gmail.com>
+eranroz <eranroz89@gmail.com>
+Erik Moeller <erik@wikimedia.org>
+Erick Guan <fantasticfears@gmail.com>
+Gilles Dubuc <gdubuc@wikimedia.org>
+Inez Korczyński <inez@wikia-inc.com>
+Juliusz Gonera <jgonera@wikimedia.org>
+Kunal Mehta <legoktm@gmail.com>
+Kyle Florence <kflorence@wikia-inc.com>
+May Tee-Galloway <mgalloway@wikimedia.org>
+Moriel Schottlender <moriel@gmail.com>
+Niklas Laxström <nlaxstrom@wikimedia.org>
+Ricordisamoa <ricordisamoa@openmailbox.org>
+rillke <rillke@wikipedia.de>
+Ryan Kaldari <rkaldari@wikimedia.org>
+Sam Reed <reedy@wikimedia.org>
+Sucheta Ghoshal <sghoshal@wikimedia.org>
+Željko Filipin <zeljko.filipin@gmail.com>
diff --git a/vendor/oojs/oojs-ui/Doxyfile b/vendor/oojs/oojs-ui/Doxyfile
new file mode 100644
index 00000000..cf831926
--- /dev/null
+++ b/vendor/oojs/oojs-ui/Doxyfile
@@ -0,0 +1,33 @@
+# Configuration file for Doxygen
+
+PROJECT_NAME = OOjs UI
+PROJECT_BRIEF = Object-Oriented JavaScript – User Interface
+
+OUTPUT_DIRECTORY = docs
+HTML_OUTPUT = php
+
+JAVADOC_AUTOBRIEF = YES
+QT_AUTOBRIEF = YES
+
+WARN_NO_PARAMDOC = YES
+
+INPUT = README.md php/
+FILE_PATTERNS = *.php
+RECURSIVE = YES
+# Requires doxygen 1.8.3+
+USE_MDFILE_AS_MAINPAGE = README.md
+
+HTML_DYNAMIC_SECTIONS = YES
+GENERATE_TREEVIEW = YES
+TREEVIEW_WIDTH = 250
+
+GENERATE_LATEX = NO
+
+HAVE_DOT = YES
+DOT_FONTNAME = Helvetica
+DOT_FONTSIZE = 10
+TEMPLATE_RELATIONS = YES
+CALL_GRAPH = NO
+CALLER_GRAPH = NO
+# Makes dot run faster. Requires graphviz >1.8.10
+DOT_MULTI_TARGETS = YES
diff --git a/vendor/oojs/oojs-ui/Gemfile.lock b/vendor/oojs/oojs-ui/Gemfile.lock
new file mode 100644
index 00000000..a5a0e197
--- /dev/null
+++ b/vendor/oojs/oojs-ui/Gemfile.lock
@@ -0,0 +1,25 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ ast (2.0.0)
+ astrolabe (1.3.0)
+ parser (>= 2.2.0.pre.3, < 3.0)
+ parser (2.2.0.pre.7)
+ ast (>= 1.1, < 3.0)
+ slop (~> 3.4, >= 3.4.5)
+ powerpack (0.0.9)
+ rainbow (2.0.0)
+ rubocop (0.27.1)
+ astrolabe (~> 1.3)
+ parser (>= 2.2.0.pre.7, < 3.0)
+ powerpack (~> 0.0.6)
+ rainbow (>= 1.99.1, < 3.0)
+ ruby-progressbar (~> 1.4)
+ ruby-progressbar (1.7.0)
+ slop (3.6.0)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ rubocop
diff --git a/vendor/oojs/oojs-ui/Gruntfile.js b/vendor/oojs/oojs-ui/Gruntfile.js
new file mode 100644
index 00000000..0aa41e42
--- /dev/null
+++ b/vendor/oojs/oojs-ui/Gruntfile.js
@@ -0,0 +1,401 @@
+/*!
+ * Grunt file
+ */
+
+/*jshint node:true */
+module.exports = function ( grunt ) {
+ grunt.loadNpmTasks( 'grunt-banana-checker' );
+ grunt.loadNpmTasks( 'grunt-contrib-clean' );
+ grunt.loadNpmTasks( 'grunt-contrib-concat-sourcemaps' );
+ grunt.loadNpmTasks( 'grunt-contrib-copy' );
+ grunt.loadNpmTasks( 'grunt-contrib-csslint' );
+ grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
+ grunt.loadNpmTasks( 'grunt-contrib-jshint' );
+ grunt.loadNpmTasks( 'grunt-contrib-less' );
+ grunt.loadNpmTasks( 'grunt-contrib-uglify' );
+ grunt.loadNpmTasks( 'grunt-contrib-watch' );
+ grunt.loadNpmTasks( 'grunt-csscomb' );
+ grunt.loadNpmTasks( 'grunt-exec' );
+ grunt.loadNpmTasks( 'grunt-file-exists' );
+ grunt.loadNpmTasks( 'grunt-cssjanus' );
+ grunt.loadNpmTasks( 'grunt-jscs' );
+ grunt.loadNpmTasks( 'grunt-karma' );
+ grunt.loadNpmTasks( 'grunt-svg2png' );
+ grunt.loadTasks( 'build/tasks' );
+
+ var modules = grunt.file.readJSON( 'build/modules.json' ),
+ pgk = grunt.file.readJSON( 'package.json' ),
+ lessFiles = {
+ raster: {},
+ vector: {},
+ mixed: {}
+ },
+ colorizeSvgFiles = {},
+ requiredFiles = modules[ 'oojs-ui' ].scripts.slice(),
+ concatCssFiles = {},
+ rtlFiles = {},
+ minBanner = '/*! OOjs UI v<%= pkg.version %> | http://oojs.mit-license.org */';
+
+ ( function () {
+ var distFile, target, module, moduleStyleFiles;
+ function rtlPath( fileName ) {
+ return fileName.replace( /\.(\w+)$/, '.rtl.$1' );
+ }
+ // Generate all task targets required to process given file into a pair of CSS files (for LTR
+ // and RTL), and return file name of LTR file.
+ function processFile( fileName ) {
+ var lessFileName, cssFileName, theme, path;
+ path = require( 'path' );
+ if ( path.extname( fileName ) === '.json' ) {
+ lessFileName = fileName.replace( /\.json$/, '.less' ).replace( /^src/, 'dist/tmp' );
+ theme = path.basename( path.dirname( fileName ) );
+
+ colorizeSvgFiles[ fileName.replace( /.+\/(\w+)\/([\w-]+)\.(?:json|less)$/, '$1-$2' ) ] = {
+ options: grunt.file.readJSON( fileName ),
+ srcDir: 'src/themes/' + theme,
+ destDir: 'dist/themes/' + theme,
+ // This should not be needed, but our dist directory structure is weird
+ cssPrependPath: 'themes/' + theme + '/',
+ destLessFile: {
+ ltr: lessFileName,
+ rtl: rtlPath( lessFileName )
+ }
+ };
+
+ cssFileName = fileName.replace( /\.json$/, '.css' ).replace( /^src/, 'dist/tmp/' + target );
+ lessFiles[ target ][ cssFileName ] = [ lessFileName ];
+ lessFiles[ target ][ rtlPath( cssFileName ) ] = [ rtlPath( lessFileName ) ];
+ } else {
+ cssFileName = fileName.replace( /\.less$/, '.css' ).replace( /^src/, 'dist/tmp/' + target );
+ lessFiles[ target ][ cssFileName ] = [ fileName ];
+ rtlFiles[ rtlPath( cssFileName ) ] = cssFileName;
+ }
+ return cssFileName;
+ }
+ for ( module in modules ) {
+ if ( modules[ module ].styles ) {
+ moduleStyleFiles = modules[ module ].styles;
+ for ( target in lessFiles ) {
+ requiredFiles.push.apply( requiredFiles, moduleStyleFiles );
+
+ distFile = 'dist/' + module + ( target !== 'mixed' ? '.' + target : '' ) + '.css';
+
+ concatCssFiles[ distFile ] = moduleStyleFiles.map( processFile );
+ concatCssFiles[ rtlPath( distFile ) ] = concatCssFiles[ distFile ].map( rtlPath );
+ }
+ }
+ }
+ }() );
+
+ function strip( str ) {
+ var path = require( 'path' );
+ // http://gruntjs.com/configuring-tasks#building-the-files-object-dynamically
+ // http://gruntjs.com/api/grunt.file#grunt.file.expandmapping
+ return function ( dest, src ) {
+ return path.join( dest, src.replace( str, '' ) );
+ };
+ }
+
+ grunt.initConfig( {
+ pkg: pgk,
+
+ // Build
+ clean: {
+ build: 'dist/*',
+ doc: 'docs/*',
+ tmp: 'dist/tmp'
+ },
+ fileExists: {
+ src: requiredFiles
+ },
+ typos: {
+ options: {
+ typos: 'build/typos.json'
+ },
+ src: '{src,php}/**/*.{js,json,less,css}'
+ },
+ concat: {
+ options: {
+ banner: grunt.file.read( 'build/banner.txt' )
+ },
+ js: {
+ files: {
+ 'dist/oojs-ui.js': modules[ 'oojs-ui' ].scripts,
+ 'dist/oojs-ui-apex.js': modules[ 'oojs-ui-apex' ].scripts,
+ 'dist/oojs-ui-mediawiki.js': modules[ 'oojs-ui-mediawiki' ].scripts
+ }
+ },
+ css: {
+ files: concatCssFiles
+ },
+ demoCss: {
+ options: {
+ banner: '/** This file is generated automatically. Do not modify it. */\n\n'
+ },
+ files: {
+ 'demos/styles/demo.rtl.css': 'demos/styles/demo.rtl.css'
+ }
+ }
+ },
+
+ // Build – Code
+ uglify: {
+ options: {
+ banner: minBanner,
+ sourceMap: true,
+ sourceMapIncludeSources: true,
+ report: 'gzip'
+ },
+ js: {
+ expand: true,
+ src: 'dist/*.js',
+ ext: '.min.js',
+ extDot: 'last'
+ }
+ },
+
+ // Build – Styling
+ less: {
+ distRaster: {
+ options: {
+ ieCompat: true,
+ report: 'gzip',
+ modifyVars: {
+ 'oo-ui-distribution': 'raster',
+ 'oo-ui-default-image-ext': 'png'
+ }
+ },
+ files: lessFiles.raster
+ },
+ distVector: {
+ options: {
+ ieCompat: false,
+ report: 'gzip',
+ modifyVars: {
+ 'oo-ui-distribution': 'vector',
+ 'oo-ui-default-image-ext': 'svg'
+ }
+ },
+ files: lessFiles.vector
+ },
+ distMixed: {
+ options: {
+ ieCompat: false,
+ report: 'gzip',
+ modifyVars: {
+ 'oo-ui-distribution': 'mixed',
+ 'oo-ui-default-image-ext': 'png'
+ }
+ },
+ files: lessFiles.mixed
+ }
+ },
+ cssjanus: {
+ options: {
+ generateExactDuplicates: true
+ },
+ dist: {
+ files: rtlFiles
+ },
+ demoCss: {
+ files: {
+ 'demos/styles/demo.rtl.css': 'demos/styles/demo.css'
+ }
+ }
+ },
+ csscomb: {
+ dist: {
+ expand: true,
+ src: 'dist/*.css'
+ }
+ },
+ copy: {
+ imagesCommon: {
+ src: 'src/styles/images/*.cur',
+ dest: 'dist/images/',
+ expand: true,
+ flatten: true
+ },
+ imagesApex: {
+ src: 'src/themes/apex/images/**/*.{png,gif}',
+ dest: 'dist/themes/apex/images/',
+ expand: true,
+ rename: strip( 'src/themes/apex/images/' )
+ },
+ imagesMediaWiki: {
+ src: 'src/themes/mediawiki/images/**/*.{png,gif}',
+ dest: 'dist/themes/mediawiki/images/',
+ expand: true,
+ rename: strip( 'src/themes/mediawiki/images/' )
+ },
+ i18n: {
+ src: 'i18n/*.json',
+ expand: true,
+ dest: 'dist/'
+ },
+ jsduck: {
+ // Don't publish devDependencies
+ src: '{dist,node_modules/{' + Object.keys( pgk.dependencies ).join( ',' ) + '}}/**/*',
+ dest: 'docs/',
+ expand: true
+ }
+ },
+ colorizeSvg: colorizeSvgFiles,
+ svg2png: {
+ dist: {
+ src: 'dist/{images,themes}/**/*.svg'
+ }
+ },
+ cssmin: {
+ options: {
+ keepSpecialComments: 0,
+ banner: minBanner,
+ compatibility: 'ie8',
+ report: 'gzip'
+ },
+ dist: {
+ expand: true,
+ src: 'dist/*.css',
+ ext: '.min.css',
+ extDot: 'last'
+ }
+ },
+
+ // Lint – Code
+ jshint: {
+ options: {
+ jshintrc: true
+ },
+ dev: [
+ '*.js',
+ '{build,demos,src,tests}/**/*.js',
+ '!tests/JSPHP.test.js'
+ ]
+ },
+ jscs: {
+ dev: [
+ '<%= jshint.dev %>',
+ '!demos/dist/**'
+ ]
+ },
+
+ // Lint – Styling
+ csslint: {
+ options: {
+ csslintrc: '.csslintrc'
+ },
+ all: [
+ '{demos,src}/**/*.css',
+ '!demos/dist/**'
+ ]
+ },
+
+ // Lint – i18n
+ banana: {
+ all: 'i18n/'
+ },
+
+ // Test
+ exec: {
+ rubyTestSuiteGenerator: {
+ command: 'ruby bin/testsuitegenerator.rb src php > tests/JSPHP-suite.json'
+ },
+ phpGenerateJSPHPForKarma: {
+ command: 'php ../bin/generate-JSPHP-for-karma.php > JSPHP.test.js',
+ cwd: './tests'
+ }
+ },
+ karma: {
+ options: {
+ frameworks: [ 'qunit' ],
+ files: [
+ 'node_modules/jquery/dist/jquery.js',
+ 'node_modules/oojs/dist/oojs.jquery.js',
+ 'dist/oojs-ui.js',
+ 'dist/oojs-ui-apex.js',
+ 'dist/oojs-ui-mediawiki.js',
+ 'tests/QUnit.assert.equalDomElement.js',
+ 'tests/**/*.test.js'
+ ],
+ reporters: [ 'dots' ],
+ singleRun: true,
+ browserDisconnectTimeout: 5000,
+ browserDisconnectTolerance: 2,
+ autoWatch: false
+ },
+ main: {
+ browsers: [ 'Chrome' ],
+ preprocessors: {
+ 'dist/*.js': [ 'coverage' ]
+ },
+ reporters: [ 'dots', 'coverage' ],
+ coverageReporter: { reporters: [
+ { type: 'html', dir: 'coverage/' },
+ { type: 'text-summary', dir: 'coverage/' }
+ ] }
+ },
+ other: {
+ browsers: [ 'Firefox' ]
+ }
+ },
+
+ // Development
+ watch: {
+ files: [
+ '<%= jshint.dev %>',
+ '<%= csslint.all %>',
+ '{demos,src}/**/*.less',
+ '.{csslintrc,jscsrc,jshintignore,jshintrc}'
+ ],
+ tasks: 'quick-build'
+ }
+ } );
+
+ grunt.registerTask( 'pre-test', function () {
+ // Only create Source maps when doing a git-build for testing and local
+ // development. Distributions for export should not, as the map would
+ // be pointing at "../src".
+ grunt.config.set( 'concat.js.options.sourceMap', true );
+ grunt.config.set( 'concat.js.options.sourceMapStyle', 'link' );
+ } );
+
+ grunt.registerTask( 'pre-git-build', function () {
+ var done = this.async();
+ require( 'child_process' ).exec( 'git rev-parse HEAD', function ( err, stout, stderr ) {
+ if ( !stout || err || stderr ) {
+ grunt.log.err( err || stderr );
+ done( false );
+ return;
+ }
+ grunt.config.set( 'pkg.version', grunt.config( 'pkg.version' ) + '-pre (' + stout.slice( 0, 10 ) + ')' );
+ grunt.verbose.writeln( 'Added git HEAD to pgk.version' );
+ done();
+ } );
+ } );
+
+ grunt.registerTask( 'build-code', [ 'concat:js', 'uglify' ] );
+ grunt.registerTask( 'build-styling', [
+ 'colorizeSvg', 'less', 'cssjanus',
+ 'concat:css', 'concat:demoCss', 'csscomb', 'cssmin',
+ 'copy:imagesCommon', 'copy:imagesApex', 'copy:imagesMediaWiki', 'svg2png'
+ ] );
+ grunt.registerTask( 'build-i18n', [ 'copy:i18n' ] );
+ grunt.registerTask( 'build-tests', [ 'exec:rubyTestSuiteGenerator', 'exec:phpGenerateJSPHPForKarma' ] );
+ grunt.registerTask( 'build', [ 'clean:build', 'fileExists', 'typos', 'build-code', 'build-styling', 'build-i18n', 'build-tests', 'clean:tmp' ] );
+
+ grunt.registerTask( 'git-build', [ 'pre-git-build', 'build' ] );
+
+ // Quickly build a no-frills vector-only ltr-only version for development
+ grunt.registerTask( 'quick-build', [
+ 'pre-git-build', 'clean:build', 'fileExists', 'typos',
+ 'concat:js',
+ 'colorizeSvg', 'less:distVector', 'concat:css',
+ 'copy:imagesCommon', 'copy:imagesApex', 'copy:imagesMediaWiki',
+ 'build-i18n'
+ ] );
+
+ grunt.registerTask( 'lint', [ 'jshint', 'jscs', 'csslint', 'banana' ] );
+ grunt.registerTask( 'test', [ 'lint', 'pre-test', 'git-build', 'karma:main', 'karma:other' ] );
+
+ grunt.registerTask( 'default', 'test' );
+};
diff --git a/vendor/oojs/oojs-ui/History.md b/vendor/oojs/oojs-ui/History.md
new file mode 100644
index 00000000..905b6b11
--- /dev/null
+++ b/vendor/oojs/oojs-ui/History.md
@@ -0,0 +1,668 @@
+# OOjs UI Release History
+
+## v0.11.3 / 2015-05-12
+* BarToolGroup: Don't use "pointer" cursor for disabled tools in enabled toolgroups (Bartosz Dziewoński)
+* Tool: Support icon+label in bar tool groups (Bartosz Dziewoński)
+* ToolGroupTool: Correct opacity of disabled nested tool group handle (Bartosz Dziewoński)
+* ToolGroupTool: Synchronize inner ToolGroup disabledness state (Bartosz Dziewoński)
+* MediaWiki theme: Add a powerful default text color for tools (Trevor Parscal)
+* MediaWiki theme: Adjust quotes icon to match other icons (nirzar)
+* MediaWiki theme: Give names to some more toolbar colours (Bartosz Dziewoński)
+* MediaWiki theme: Provide all variants of the 'tag' icon (James D. Forrester)
+* MediaWiki theme: Rejigger some toolbar coloring (Bartosz Dziewoński)
+* MediaWiki theme: Remove box-shadow from nested toolbars (Bartosz Dziewoński)
+* MediaWiki theme: Remove unusued toolbar shadow (Trevor Parscal)
+* MediaWiki theme: Update button specification (nirzar)
+
+## v0.11.2 / 2015-05-11
+* Don't select lookup items on initialize (Ed Sanders)
+* ListToolGroup, MenuToolGroup: Set accelTooltips = false (Bartosz Dziewoński)
+* PopupWidget: Add setAlignment (Moriel Schottlender)
+* SelectWidget: Mark as @abstract, which it is (Bartosz Dziewoński)
+* Simplify default action prevention in buttons and forms (Bartosz Dziewoński)
+* TabOptionWidget: Fix disabled styles to not react to hover/select (Ed Sanders)
+* TextInputWidget: Allow override of #setValidityFlag (Ed Sanders)
+* TextInputWidget: Use aria-required along with the required attribute (Prateek Saxena)
+* Toolbar: Fix shadow styling (Bartosz Dziewoński)
+* Toolbar: Move some tweaks from demo to actual implementation (Bartosz Dziewoński)
+* Toolbar: Remove some useless code from the example (Bartosz Dziewoński)
+* Toolbar: Rework example and add 'menu' tool group example (Bartosz Dziewoński)
+* MediaWiki theme: Change highlight color for selected menu option (nirzar)
+* MediaWiki theme: Polish the toolbar design (nirzar)
+* MediaWiki theme: Remove accidentally duplicated styles for SelectWidget (Bartosz Dziewoński)
+
+## v0.11.1 / 2015-05-04
+* Add IndexLayout (Trevor Parscal)
+* CardLayout: Fix typo (Kirsten Menger-Anderson)
+* LabelElement: Document that label config option can take an HtmlSnippet (Roan Kattouw)
+* PopupButtonWidget: Update align config in example (Kirsten Menger-Anderson)
+* Remove GridLayout remnants (Bartosz Dziewoński)
+* SelectWidget: Add #selectItemByData method (Moriel Schottlender)
+* TabOptionWidget: Change link to card layout (Kirsten Menger-Anderson)
+* TextInputWidget: Annotate input validation with aria-invalid (Prateek Saxena)
+* TextInputWidget: Don't set 'invalid' flag on first focus, even if invalid (Bartosz Dziewoński)
+* TextInputWidget: Support 'required' config option in PHP (Bartosz Dziewoński)
+* MediaWiki theme: Add 'destructive' variant to block icon (Moriel Schottlender)
+* MediaWiki theme: Better vertical alignment of 'search' icon (Ed Sanders)
+* MediaWiki theme: Tweak 'search' icon size (Ed Sanders)
+* MediaWiki theme: Use variable for transition time and easing function (Prateek Saxena)
+* MediaWiki theme: input: Use variable for transition time and easing function (Prateek Saxena)
+* MediaWiki theme: radio/checkbox: Use variable for transition time and easing function (Prateek Saxena)
+* MediaWiki, Apex themes: Switch icons: clear → cancel, closeInput → clear (Bartosz Dziewoński)
+* MediaWiki, Apex themes: Switch over 'magnifyingGlass' icon to be 'search' (James D. Forrester)
+* build: Add clean:doc task (Bartosz Dziewoński)
+* build: Bump grunt-jscs to latest version (James D. Forrester)
+* core: Add OO.ui.debounce() utility (Roan Kattouw)
+* demo: Add icons with variants to icons demo (Bartosz Dziewoński)
+
+## v0.11.0 / 2015-04-29
+* [BREAKING CHANGE] Do not set font-size: 0.8em anywhere in the library (Bartosz Dziewoński)
+
+* [DEPRECATING CHANGE] Create rtl-ready alignments in PopupWidget (Moriel Schottlender)
+
+* MediaWiki theme: Adding variants to several icons (Moriel Schottlender)
+* TextInputWidget: Allow functions to be passed as 'validate' config option (Bartosz Dziewoński)
+* TextInputWidget: Styles for 'invalid' flag (Bartosz Dziewoński)
+* Update OOjs to v1.1.7 (James D. Forrester)
+* Update jQuery from v1.11.1 to v1.11.3 (James D. Forrester)
+* build: Use jquery and oojs from npm instead of embedded lib (Timo Tijhof)
+
+## v0.10.1 / 2015-04-27
+* Correct 'tabindex' attribute setting (Bartosz Dziewoński)
+* Make toolbars keyboard-accessible (Bartosz Dziewoński)
+* ToggleButtonWidget: Unbreak horizontal alignment (Bartosz Dziewoński)
+
+## v0.10.0 / 2015-04-22
+* [BREAKING CHANGE] ButtonWidget: remove deprecated nofollow option alias (C. Scott Ananian)
+* [BREAKING CHANGE] Convert ToggleWidget from a mixin to an abstract class (Bartosz Dziewoński)
+* [BREAKING CHANGE] MenuLayout: Reimplement without inline styles (Bartosz Dziewoński)
+
+* BarToolGroup: Allow tools with labels instead of icons (Bartosz Dziewoński)
+* BookletLayout: Find first focusable element and add focusable utility (Moriel Schottlender)
+* ButtonInputWidget: Don't double-mixin FlaggedElement (Bartosz Dziewoński)
+* ButtonWidget: Remove href to make unclickable when disabled (Bartosz Dziewoński)
+* ButtonWidget: Remove pointless #isHyperlink property (Bartosz Dziewoński)
+* FormLayout: Better document how this works with InputWidgets (Bartosz Dziewoński)
+* MenuLayout: Add example (Kirsten Menger-Anderson)
+* MenuLayout: Fix initialization order (Bartosz Dziewoński)
+* PHP: More useful debugging information on unsafe tag attributes (Chad Horohoe)
+* SelectWidget#getTargetItem: Simplify (Ed Sanders)
+* Toolbar: Add example (Bartosz Dziewoński)
+* themes: Add viewCompact, viewDetails, visionSimulator icons for iconography page (Mun May Tee)
+* demo: Remove VisualEditor references from toolbar demo, use generic icons (Ed Sanders)
+* demo: Remove outline controls from outlined BookletLayout demo (Bartosz Dziewoński)
+* demo: Simplify ButtonGroupWidget and ButtonSelectWidget examples (Bartosz Dziewoński)
+
+## v0.9.8 / 2015-04-12
+* Apex, MediaWiki: Correct or delete unused SVG files (James D. Forrester)
+* Apex theme: Correctly position popups in RTL; follows-up v0.9.5 (Moriel Schottlender)
+* BookletLayout: Allow focus on any item (Moriel Schottlender)
+* Error: Add description (Kirsten Menger-Anderson)
+* Move coverage output from "/dist/coverage" to "/coverage" (Timo Tijhof)
+* ProcessDialog: Remove stray 'this.$' from documentation code example (Roan Kattouw)
+* ProgressBarWidget: Remove spurious styles from CSS output (Bartosz Dziewoński)
+* build: Add explicit dependency upon grunt-cli (Kunal Mehta)
+* build: Run lint before build in grunt-test (Timo Tijhof)
+* colorize-svg: Generate language-specific rules for images even if equal to default ones (Bartosz Dziewoński)
+* colorize-svg: Sprinkle `/* @noflip */` on language-specific rules (Bartosz Dziewoński)
+* demo: Change html dir property when direction changes (Moriel Schottlender)
+
+## v0.9.7 / 2015-04-03
+* build: Generate correct paths to fallback images (Bartosz Dziewoński)
+
+## v0.9.5 / 2015-04-02
+* [DEPRECATING CHANGE] Deprecate search widget event re-emission (Ed Sanders)
+
+* ActionFieldLayout: Add description and example (Kirsten Menger-Anderson)
+* Add vertical spacing to RadioSelectWidget in MW theme (Ed Sanders)
+* Allow rejecting Process with single Error (Matthew Flaschen)
+* Apex theme: Tweak 'check.svg' syntax (Bartosz Dziewoński)
+* Balance padding now that focus highlight is balanced (Ed Sanders)
+* BookletLayout: Add description and example (Kirsten Menger-Anderson)
+* Bring in remaining VisualEditor icons for Apex and MediaWiki themes (James D. Forrester)
+* Choose can't emit with a null item (Ed Sanders)
+* Correctly position popups in RTL (Moriel Schottlender)
+* Fix opacity of icons/indicators in disabled DecoratedOptionWidget (Ed Sanders)
+* IconWidget: Mix in FlaggedElement (Bartosz Dziewoński)
+* Increase specificity of ButtonElement icon and indicator styles (Bartosz Dziewoński)
+* Make colorize-svg.js actually work more often (Bartosz Dziewoński)
+* MediaWiki theme: Allow intention flags for non-buttons (Andrew Garrett)
+* MediaWiki theme: Fix icon opacity for disabled ButtonOptionWidgets (Bartosz Dziewoński)
+* MediaWiki theme: Use checkbox icon per mockups (Bartosz Dziewoński)
+* MediaWiki, Apex: Provide an RTL variant for the help icon (James D. Forrester)
+* MenuLayout: Correct documentation (Bartosz Dziewoński)
+* OutlineOption: Add description (Kirsten Menger-Anderson)
+* PageLayout: Add description (Kirsten Menger-Anderson)
+* Process: Add description (Kirsten Menger-Anderson)
+* Properly support LTR/RTL icon versions in colorize-svg.js (Bartosz Dziewoński)
+* Refactor icon handling again (Bartosz Dziewoński)
+* Remove line height reset for windows (Ed Sanders)
+* Restore font family definitions to form elements (Ed Sanders)
+* Revert "Button styles between OOJS and MW" (Bartosz Dziewoński)
+* StackLayout: Add description and example (Kirsten Menger-Anderson)
+* build: Add a 'generated automatically' banner to demo.rtl.css (Bartosz Dziewoński)
+* build: Generate prettier task names for 'colorizeSvg' (Bartosz Dziewoński)
+* build: Have separate 'cssjanus' target for demo.rtl.css (Bartosz Dziewoński)
+* build: Simplify 'fileExists' task configuration (Bartosz Dziewoński)
+* build: Support (poorly) per-language icon versions in colorize-svg.js (Bartosz Dziewoński)
+* build: Update grunt-banana-checker to v0.2.1 (James D. Forrester)
+
+## v0.9.4 / 2015-03-25
+* ButtonElement: Clarify description (Kirsten Menger-Anderson)
+* ButtonElement: Disable line wrapping on buttons (Ed Sanders)
+* Compensate for loss of margin when opening modals (Ed Sanders)
+* FieldLayout: Clarify description and mark private methods (Kirsten Menger-Anderson)
+* FieldsetLayout: Add description and example (Kirsten Menger-Anderson)
+* FormLayout: Add description, example, and mark private method (Kirsten Menger-Anderson)
+* Layout: Add description (Kirsten Menger-Anderson)
+* LookupElement: Add description and mark private and protected methods (Kirsten Menger-Anderson)
+* LookupElement: Fix typo in docs (Bartosz Dziewoński)
+* Make outline controls abilities configurable (Trevor Parscal)
+* MenuLayout: Reorder styles (Bartosz Dziewoński)
+* MenuSectionOptionWidget: Add description and example (Kirsten Menger-Anderson)
+* ProcessDialog#executeAction: Don't eat parent's return value (Roan Kattouw)
+* PanelLayout: Add description and example (Kirsten Menger-Anderson)
+* Reduce thickness of toolbar border in MediaWiki (Ed Sanders)
+* SearchWidget: Add description and mark private methods (Kirsten Menger-Anderson)
+* TabIndexElement: Mark private method (Kirsten Menger-Anderson)
+
+## v0.9.3 / 2015-03-19
+* Add .mailmap file (Roan Kattouw)
+* Add Kirsten to AUTHORS.txt (Roan Kattouw)
+* Dialog: Fix links to static properties (Kirsten Menger-Anderson)
+* DraggableGroupElement: Clarify description and mark private methods (Kirsten Menger-Anderson)
+* Fix code style in @examples (Ed Sanders)
+* FlaggedElement: Add example and clarify description (Kirsten Menger-Anderson)
+* GroupElement: Clarify description (Kirsten Menger-Anderson)
+* IndicatorElement: Clarify description (Kirsten Menger-Anderson)
+* LookupElement: Add optional config field for suggestions when empty (Matthew Flaschen)
+* MenuSelectWidget: Clarify description (Kirsten Menger-Anderson)
+* ProcessDialog: send an array to showErrors in failed executeAction (Moriel Schottlender)
+* TabIndexedElement: Clarify description (Kirsten Menger-Anderson)
+* TitledElement: Clarify description (Kirsten Menger-Anderson)
+* Update OOjs to v1.1.6 (James D. Forrester)
+* Widget: Clarify description (Kirsten Menger-Anderson)
+* Window: Clarify description of setDimensions method (Kirsten Menger-Anderson)
+* WindowManager: Clarify description and mark private methods (Kirsten Menger-Anderson)
+* demo: Add one more toolbars demo (Bartosz Dziewoński)
+
+## v0.9.2 / 2015-03-12
+* Toolbar: Be less aggressive with 'white-space: nowrap' (Bartosz Dziewoński)
+* Window: Revert changes from 521061dd (Bartosz Dziewoński)
+
+## v0.9.1 / 2015-03-11
+* Syncing some button styles with MediaWiki UI (kaldari)
+* MediaWiki Theme: Add the progressive variant to the check icon (Prateek Saxena)
+* demo: Fix typo in toolbars demo (Bartosz Dziewoński)
+* TextInputWidget: Use MutationObserver for #onElementAttach support (Bartosz Dziewoński)
+* TextInputWidget: Adjust size and label on first focus, too (Bartosz Dziewoński)
+* jsduck: Add MouseEvent and KeyboardEvent to externals (Timo Tijhof)
+* jsduck: Set --processes=0 to fix warnings-exit-nonzero (Timo Tijhof)
+* Dialog: Mark private methods and add description of methods and configs (Kirsten Menger-Anderson)
+* ProcessDialog: Add description and example and mark private methods (Kirsten Menger-Anderson)
+* MessageDialog: Add description, example, and mark private methods (Kirsten Menger-Anderson)
+* build: Remove obsolete 'build' task from grunt-doc (Timo Tijhof)
+* build: Move pre/post 'doc' task into package.json (Timo Tijhof)
+* Remove remnants of window isolation (Bartosz Dziewoński)
+* demo: Simplify @media styles (Bartosz Dziewoński)
+* PanelLayout: Add 'framed' config option (Bartosz Dziewoński)
+* TextInputMenuSelectWidget: Add description and mark private methods (Kirsten Menger-Anderson)
+* Toolbar: Tighten whitespace on narrow displays (Bartosz Dziewoński)
+* demo: Use popup with head in the toolbars demo (Bartosz Dziewoński)
+* Remove half-baked touch event handling (Bartosz Dziewoński)
+* ButtonElement: Use #setButtonElement correctly (Bartosz Dziewoński)
+* WindowManager: Documentation typo (Ed Sanders)
+* ButtonInputWidget: Clarify description of configs and methods (Kirsten Menger-Anderson)
+* Icon width should only be applied if there is an icon (Moriel Schottlender)
+* package.json: Bump grunt-svg2png to 0.2.7 (Bartosz Dziewoński)
+* Add warning variant to MediaWiki set (Mark Holmquist)
+* Button styles between OOJS and MW (nirzar)
+* AUTHORS: Add Derk-Jan Hartman (Derk-Jan Hartman)
+* ActionSet: Add description for events and clarify method descriptions (Kirsten Menger-Anderson)
+* demo: Load styles before building demo widgets (not asynchronously) (Bartosz Dziewoński)
+* ActionWidget: Fix bad copy-paste in documentation (Bartosz Dziewoński)
+* Window: Clarify descriptions of methods and configs (Kirsten Menger-Anderson)
+* OutlineSelectWidget: Add description (Kirsten Menger-Anderson)
+* OutlineControlsWidget: Add description (Kirsten Menger-Anderson)
+* MediaWiki Theme: Add "Wikicon" icons (James D. Forrester)
+* build: Set 'generateExactDuplicates: true' for CSSJanus (Bartosz Dziewoński)
+* build: Implement basic image flipping support in colorize-svg (Bartosz Dziewoński)
+* ActionWidget: Clarify description and mark private method (Kirsten Menger-Anderson)
+* ActionSet: Clarify description (Kirsten Menger-Anderson)
+* InputWidget: Clarify description (Kirsten Menger-Anderson)
+* MediaWiki Theme: textInputWidget: Update focus state (Prateek Saxena)
+* Only prevent default for handled keypresses (Brad Jorsch)
+
+## v0.9.0 / 2015-03-04
+* [BREAKING CHANGE] Remove innerOverlay (Ed Sanders)
+* [BREAKING CHANGE] TextInputWidget: Remove 'icon' and 'indicator' events (Bartosz Dziewoński)
+* [BREAKING CHANGE] Remove deprecated LookupInputWidget (Bartosz Dziewoński)
+* [BREAKING CHANGE] Remove deprecated GridLayout (Bartosz Dziewoński)
+
+* Only modify body class when first/last window opens/closes (Ed Sanders)
+* InputWidget: Focus checkboxes and radios, too, when the label is clicked (Bartosz Dziewoński)
+* Move `OO.ui.infuse` to `OO.ui.Element.static.infuse`. (C. Scott Ananian)
+* Don't call LabelElement constructor twice for ActionFieldLayouts (Roan Kattouw)
+* PopupElement: Add description (Kirsten Menger-Anderson)
+* Make icon and indicator container sizes consistent (Ed Sanders)
+* SelectWidget: Marked protected methods and clarified choose/press descriptions (Kirsten Menger-Anderson)
+* demo: Call Toolbar#initialize in toolbar demo (Bartosz Dziewoński)
+* demo: Add PopupTool to toolbar demo (Bartosz Dziewoński)
+* PopupTool: Tool constructor takes a toolGroup, not a toolbar (Bartosz Dziewoński)
+* Infer retry button action flags from symbolic name (Trevor Parscal)
+* Revert "Unbreak form submission in JavaScript" (Bartosz Dziewoński)
+* PopupWidget: Add description, example, and mark private methods (Kirsten Menger-Anderson)
+* ToggleButtonWidget: Add description, example, and mark private method (Kirsten Menger-Anderson)
+* ToggleWidget: Add description (Kirsten Menger-Anderson)
+* ToggleSwitchWidget: Add description, example, and mark private methods (Kirsten Menger-Anderson)
+* build: Add disconnect tolerance to karma config (James D. Forrester)
+* ProgressBar: Add description and example (Kirsten Menger-Anderson)
+* CheckboxInputWidget: Add description and example (Kirsten Menger-Anderson)
+* Use the correct color for gray buttons (Prateek Saxena)
+* RadioInputWidget: Add description and example (Kirsten Menger-Anderson)
+* Fix invalid use of border shorthand syntax (Timo Tijhof)
+* Restore previous toolbar items margins and padding (Bartosz Dziewoński)
+* ProcessDialog: Dismiss errors on teardown (Moriel Schottlender)
+* build: Remove footer override from jsduck (Timo Tijhof)
+* FieldLayout: Fix display of bulleted list (Kirsten Menger-Anderson)
+* Use only two variables each for each semantic color (Prateek Saxena)
+* tests: Run JS/PHP tests for widgets with required parameters, too (Bartosz Dziewoński)
+* TextInputWidget: Add description, example, and mark private methods (Kirsten Menger-Anderson)
+* tests: Add infusion tests (Bartosz Dziewoński)
+* PopupWidget: Add keydown listener and hide popup on ESC (Prateek Saxena)
+* DropdownInputWidget: Add description, example, and mark private method (Kirsten Menger-Anderson)
+* ComboBoxWidget: Add description, example, and mark private methods (Kirsten Menger-Anderson)
+* DecoratedOptionWidget: Add description and example (Kirsten Menger-Anderson)
+* SelectWidget: Add example and link to decorated option widget (Kirsten Menger-Anderson)
+* GroupWidget and ItemWidget: Mark private (Kirsten Menger-Anderson)
+* Fake toolbar group nesting (Bartosz Dziewoński)
+* LabelWidget: Add description, example, and mark private method (Kirsten Menger-Anderson)
+* IndicatorWidget: Add description and example (Kirsten Menger-Anderson)
+
+## v0.8.3 / 2015-02-26
+* Revert "Unbreak form submission in JavaScript" (Bartosz Dziewoński)
+
+## v0.8.2 / 2015-02-26
+* testsuitegenerator: Exclude 'text' parameter from tests, like 'content' (Bartosz Dziewoński)
+* PHP TitledElement: Actually set $this->title (Bartosz Dziewoński)
+* PHP PanelLayout: Fix getConfig() for 'expanded' config option (Bartosz Dziewoński)
+* WindowManager: Don't pass 'this' to window factory method (Bartosz Dziewoński)
+
+## v0.8.1 / 2015-02-25
+* [DEPRECATING CHANGE] Rename setPosition to setLabelPosition (Ed Sanders)
+
+* Element.php: Add "data" property (C. Scott Ananian)
+* Element.php: Add "text" configuration option (C. Scott Ananian)
+* Work around Safari 8 misrendering checkboxes in SVG-only distribution (Bartosz Dziewoński)
+* TextInputWidget: Fix appearance of icons and labels when disabled (Ed Sanders)
+* Remove '$: this.$' from code examples (Bartosz Dziewoński)
+* Prefer OO.isPlainObject to $.isPlainObject (Bartosz Dziewoński)
+* Error: Fix function name (Bartosz Dziewoński)
+* build: Use grunt-contrib-copy instead of custom 'copy' task (Timo Tijhof)
+* Element.php: Tweak docs (Bartosz Dziewoński)
+* docparser.rb: Ruby 1.9.3 compatibility (Bartosz Dziewoński)
+* Move toggle() from Widget to Element (Moriel Schottlender)
+* build: Include 'lib' and 'dist' in jsduck output (Timo Tijhof)
+* Fix typo: contian → contain (Bartosz Dziewoński)
+* MediaWiki Theme: Drop unnecessary pseudo-element of CheckboxInputWidget (Timo Tijhof)
+* MediaWiki Theme: Drop unnecessary pseudo-element of RadioInputWidget (Timo Tijhof)
+* MediaWiki theme: Simplify spacing of checkboxes/radios in FieldLayouts (Bartosz Dziewoński)
+* Add disabled RadioInputWidget to demo (Bartosz Dziewoński)
+* RadioOptionWidget: Make disabling single options work (Bartosz Dziewoński)
+* composer.json: Add description field (Kunal Mehta)
+* IconElement: Add description for config options (Kirsten Menger-Anderson)
+* IndicatorElement: Add description for configs and static properties (Kirsten Menger-Anderson)
+* DraggableElement: Mark private methods and add description to events (Kirsten Menger-Anderson)
+* Element: Add description for configs and static property (Kirsten Menger-Anderson)
+* LabelElement: Add description, config description, static property description (Kirsten Menger-Anderson)
+* TitledElement: Add description and config and static descriptions (Kirsten Menger-Anderson)
+* ComboBox: Use combobox role (Derk-Jan Hartman)
+* IconElement: Add description of methods (Kirsten Menger-Anderson)
+* Follow-up bade83bfdfc: actually remove ../ (Roan Kattouw)
+* Remove loop length check (Ed Sanders)
+* PopupButtonWidget: Add description and example and mark private method (Kirsten Menger-Anderson)
+* FlaggedElement: Add description of event and config option (Kirsten Menger-Anderson)
+* Unbreak docparser.rb (Bartosz Dziewoński)
+* Allow passing positional parameters inside the config object (Bartosz Dziewoński)
+* Run JS/PHP comparison tests using karma (Bartosz Dziewoński)
+* test: Reduce timeout in Process test from 100 to 10 (Timo Tijhof)
+* OptionWidget: Add description and descriptions of methods (Kirsten Menger-Anderson)
+* FormLayout: Allow adding child layouts via config (Bartosz Dziewoński)
+* Teach docparser about @member, @see, and PHP pass-by-reference (&$foo). (C. Scott Ananian)
+* Element: Add `content` config option, matching PHP side. (C. Scott Ananian)
+* SelectWidget: Add description for config, methods, events (Kirsten Menger-Anderson)
+* Serialize PHP widget state into data-ooui attribute (C. Scott Ananian)
+* Implement OO.ui.infuse to reconstitute PHP widgets in client-side JS (C. Scott Ananian)
+* ButtonSelectWidget: Add description and example (Kirsten Menger-Anderson)
+* RadioOptionWidget: Add description (Kirsten Menger-Anderson)
+* ButtonOptionWidget: Add description (Kirsten Menger-Anderson)
+* RadioSelectWidget: Add description and example (Kirsten Menger-Anderson)
+* Set proper spacing between interleaved FieldsetLayouts and FormLayouts (Bartosz Dziewoński)
+* MenuOptionWidget: Add description (Kirsten Menger-Anderson)
+* Unbreak form submission in JavaScript (Bartosz Dziewoński)
+* MenuSelectWidget: Add description and mark protected method (Kirsten Menger-Anderson)
+* TabIndexelement: Add description, example, and mark private method (Kirsten Menger-Anderson)
+* Add "composer test" command to lint PHP files and run phpcs (Kunal Mehta)
+* Update OOjs to v1.1.5 (James D. Forrester)
+
+
+## v0.8.0 / 2015-02-18
+* [BREAKING CHANGE] Make default distribution provide SVG with PNG fallback (Bartosz Dziewoński)
+
+* [DEPRECATING CHANGE] TextInputWidget: Deprecate 'icon' and 'indicator' events (Bartosz Dziewoński)
+
+* ButtonElement: add protected to event handlers (Kirsten Menger-Anderson)
+* docs: Make @example documentation tag work (Roan Kattouw)
+* TextInputWidget: Hide mixin components when unused (Ed Sanders)
+* DropdownWidget: Simplify redundant code (Bartosz Dziewoński)
+* Update PHP widgets for accessibility-related changes in JS widgets (Bartosz Dziewoński)
+* TabIndexedElement: Allow tabIndex property to be null (C. Scott Ananian)
+* ButtonElement: Add description (Kirsten Menger-Anderson)
+* Add missing ButtonInputWidget.less and corresponding mixin (Bartosz Dziewoński)
+* Various fixes to the PHP implementation (C. Scott Ananian)
+* Use Array.isArray instead of $.isArray (C. Scott Ananian)
+* TextInputWidget: Use margins for moving the label (Ed Sanders)
+* DraggableGroupElement: Add description (Kirsten Menger-Anderson)
+* demo: Add horizontal alignment test (Bartosz Dziewoński)
+* build: Pass RuboCop, customize settings (Bartosz Dziewoński)
+* Widget: Add description (Kirsten Menger-Anderson)
+* ButtonInputWidget: Add description and example (Kirsten Menger-Anderson)
+* Dialog: Add description and example (Kirsten Menger-Anderson)
+* DraggableElement: Add description (Kirsten Menger-Anderson)
+* docparser: Add support for 'protected' methods (Bartosz Dziewoński)
+* testsuitegenerator: Only test every pair of config options rather than every triple (Bartosz Dziewoński)
+* TextInputWidget: Don't add label position classes when there's no label (Bartosz Dziewoński)
+* Update JS/PHP comparison test suite (Bartosz Dziewoński)
+* tests: Fix the check for properties (Bartosz Dziewoński)
+* TextInputWidget: Add missing LabelElement mixin documentation (Ed Sanders)
+* Follow-up c762da42: fix ProcessDialog error handling (Roan Kattouw)
+* MediaWiki Theme: Add focus state for frameless button (Prateek Saxena)
+* TextInputWidget: Allow maxLength of 0 in JS (matching PHP) (Bartosz Dziewoński)
+* TextInputWidget: Only put $label in the DOM if needed (Bartosz Dziewoński)
+* MediaWiki Theme: Use white icons for disabled buttons (Bartosz Dziewoński)
+* Follow-up 6a6bb90ab: Update CSS file path in eg-iframe.html (Roan Kattouw)
+* Element: Add description (Kirsten Menger-Anderson)
+* PHP: Remove redundant documentation for getInputElement() (Bartosz Dziewoński)
+* Some documentation tweaks (Bartosz Dziewoński)
+* FieldLayout: Add description (Kirsten Menger-Anderson)
+* FieldLayout: Clean up and remove lies (Bartosz Dziewoński)
+* FlaggedElement: Add description (Kirsten Menger-Anderson)
+* PHP demo: Correct path to CSS files (Bartosz Dziewoński)
+* MediaWikiTheme: Resynchronize PHP with JS (Bartosz Dziewoński)
+* ButtonWidget: Rename nofollow config option to noFollow (C. Scott Ananian)
+* GroupElement: Add description (Kirsten Menger-Anderson)
+* IconElement: Add description (Kirsten Menger-Anderson)
+* IconWidget: Add description and example (Kirsten Menger-Anderson)
+* IndicatorElement: Add description (Kirsten Menger-Anderson)
+* InputWidget: Add description (Kirsten Menger-Anderson)
+* SelectWidget: Add description (Kirsten Menger-Anderson)
+* MediaWiki Theme: Fix border width for frameless buttons' focus state (Prateek Saxena)
+* Window: Add description (Kirsten Menger-Anderson)
+* WindowManager: Add description (Kirsten Menger-Anderson)
+* ButtonGroupWidget: Add description and example (Kirsten Menger-Anderson)
+* DropdownWidget: Add @private to private methods (Kirsten Menger-Anderson)
+* Refactor keyboard accessibility of SelectWidgets (Bartosz Dziewoński)
+* ActionSet: Add description and example (Kirsten Menger-Anderson)
+* ActionSet: Add @private to onActionChange method (Kirsten Menger-Anderson)
+* ActionWidget: Add description (Kirsten Menger-Anderson)
+* ActionSet: Add description for specialFlags property (Kirsten Menger-Anderson)
+* DropdownWidget: Add description and example (Kirsten Menger-Anderson)
+* ButtonWidget: Add example and link (Kirsten Menger-Anderson)
+* IconElement: Add description and fix display of static properties (Kirsten Menger-Anderson)
+
+
+## v0.7.0 / 2015-02-11
+* [BREAKING CHANGE] Remove window isolation (Trevor Parscal)
+
+* [DEPRECATING CHANGE] GridLayout should no longer be used, instead use MenuLayout (Bartosz Dziewoński)
+
+* Fade in window frames separately from window overlays (Ed Sanders)
+* Fix initialisation of window visible (Ed Sanders)
+* SelectWidget: 'listbox' wrapper role, 'aria-selected' state on contents (Derk-Jan Hartman)
+* Cleanup unreachable code from DraggableGroupElement (Moriel Schottlender)
+* DraggableGroupElement: Unset dragged item when dropped (Moriel Schottlender)
+* Remove inline spacing from ButtonWidget (Roan Kattouw)
+* Make sure DraggableGroupElement supports button widgets (Moriel Schottlender)
+* demo: Use properties instead of attributes for <link> (Timo Tijhof)
+* Revert "Remove inline spacing from ButtonWidget" (Bartosz Dziewoński)
+* ToggleSwitchWidget: Accessibility improvements (Bartosz Dziewoński)
+* TextInputWidget: Add required attribute on the basis of required config (Prateek Saxena)
+* DropdownInputWidget: Fix undefined variable in PHP (Bartosz Dziewoński)
+* PHP demo: Just echo the autoload error message, don't trigger_error() (Bartosz Dziewoński)
+* demo: Stop inline consoles from generating white space (Bartosz Dziewoński)
+* demo: Reorder widgets into somewhat logical groupings (Bartosz Dziewoński)
+* demo: Add button style showcase from PHP demo (Bartosz Dziewoński)
+* PHP demo: Resynchronize with JS demo (Bartosz Dziewoński)
+* Stop treating ApexTheme class unfairly and make it proper (Bartosz Dziewoński)
+* PHP demo: Add Vector/Raster and MediaWiki/Apex controls (Bartosz Dziewoński)
+* Delete unused src/themes/apex/{raster,vector}.less (Bartosz Dziewoński)
+* {Checkbox,Radio}InputWidget: Add missing configuration initialization (Bartosz Dziewoński)
+* MediaWiki theme: Use distribution's image type for backgrounds (Bartosz Dziewoński)
+* tests: Just echo the autoload error message, don't trigger_error() (Bartosz Dziewoński)
+* MediaWiki theme: Fix non-clickability of radios and checkboxes (Bartosz Dziewoński)
+* Fix text input auto-height calculation (Ed Sanders)
+* MediaWiki theme: Consistent toggle button 'active' state (Bartosz Dziewoński)
+* RadioOptionWidget: Make it a <label/> (Bartosz Dziewoński)
+* MediaWiki theme: Correct flagged primary button text color when pressed (Bartosz Dziewoński)
+* FieldsetLayout: Tweak positioning of help icon (Bartosz Dziewoński)
+* TextInputWidget: Use aria-hidden for extra autosize textarea (Prateek Saxena)
+* Refactor clickability of buttons (Bartosz Dziewoński)
+* Remove usage of this.$ and config.$ (Trevor Parscal)
+* build: Bump various devDependencies (James D. Forrester)
+* History: Fix date typos (James D. Forrester)
+* TabIndexedElement: Actually allow tabIndex of -1 (Bartosz Dziewoński)
+* ListToolGroup: Remove hack for jQuery's .show()/.hide() (Bartosz Dziewoński)
+* PopupWidget: Set $clippable only once, correctly (Bartosz Dziewoński)
+* TextInputMenuSelectWidget: Correct documentation (Bartosz Dziewoński)
+* PopupElement: Correct documentation (Bartosz Dziewoński)
+* MediaWiki Theme: Rename @active to @pressed in button mixins (Prateek Saxena)
+* tools.less: Use distribution's image type and path for background (Prateek Saxena)
+* MediaWiki Theme: Fix background color for disabled buttons (Prateek Saxena)
+* MenuSelectWidget: Don't clobber other events when unbinding (Bartosz Dziewoński)
+* MenuSelectWidget: Codify current behavior of Tab closing the menu (Bartosz Dziewoński)
+* DropdownWidget, ComboBoxWidget: Make keyboard-accessible (Bartosz Dziewoński)
+* MenuSelectWidget: Remove dead code (Bartosz Dziewoński)
+* MediaWiki Theme: Rename active-* variables to pressed-* (Prateek Saxena)
+* ButtonWidget: Better handle non-string parameters in setHref/setTarget (C. Scott Ananian)
+* Make better use of 'scrollIntoViewOnSelect' in OptionWidgets (Bartosz Dziewoński)
+* ButtonWidget: Add "nofollow" option (C. Scott Ananian)
+* MediaWiki Theme: Rename @highlight to @active (Prateek Saxena)
+* MediaWiki Theme: Use darker color for frameless buttons (Prateek Saxena)
+* ButtonWidget: Add documentation (Kirsten Menger-Anderson)
+
+## v0.6.6 / 2015-02-04
+* TextInputWidget: Mostly revert "Don't try adjusting size when detached" (Bartosz Dziewoński)
+* Use css class instead of jQuery .show()/hide()/toggle() (Moriel Schottlender)
+* build: Use karma to v0.12.31 (Timo Tijhof)
+* Use standard border colours for progress bars (Ed Sanders)
+* Remove disabled elements from keyboard navigation flow (Derk-Jan Hartman)
+* Fix BookletLayout#toggleOutline to use MenuLayout method (Ed Sanders)
+* Use CSS overriding trick to support RTL in menu layouts (Ed Sanders)
+
+## v0.6.5 / 2015-02-01
+* Make BookletLayout inherit from MenuLayout instead of embedding a GridLayout (Ed Sanders)
+* ButtonElement: Unbreak 'pressed' state (Bartosz Dziewoński)
+
+## v0.6.4 / 2015-01-30
+* InputWidget: Resynchronize our internal .value with DOM .value in #getValue (eranroz)
+* demo: Remove nonexistent 'align' config option for a DropdownWidget (Bartosz Dziewoński)
+* MediaWiki theme: Reduce size of checkboxes and radio buttons by 20% (Ed Sanders)
+* MediaWiki theme: Remove SearchWidget's border now dialogs have outline (Ed Sanders)
+* TextInputWidget: Accept 'maxLength' configuration option (Bartosz Dziewoński)
+* MediaWiki Theme: Adjust ButtonSelectWidget, ButtonGroupWidget highlights (Prateek Saxena)
+* Update OOjs to v1.1.4 and switch to the jQuery-optimised version (James D. Forrester)
+* build: Bump devDependencies and fix up (James D. Forrester)
+* Seriously work around the Chromium scrollbar bug for good this time (Bartosz Dziewoński)
+* Introduce and use TabIndexedElement (Bartosz Dziewoński)
+* AUTHORS: Update for the last six months' work (James D. Forrester)
+* Set input direction in html prop rather than css rule (Moriel Schottlender)
+* Introduce DropdownInputWidget (Bartosz Dziewoński)
+* Remove the 'flash' feature from MenuSelectWidget and OptionWidget (Bartosz Dziewoński)
+* InputWidget: Clarify documentation of #getInputElement (Bartosz Dziewoński)
+* Make sure there is a page before focusing in BookletLayout (Moriel Schottlender)
+* Provide default margins for buttons and other widgets (Bartosz Dziewoński)
+* OptionWidget: Unbreak 'pressed' state (Bartosz Dziewoński)
+* TextInputWidget: Remove superfluous role=textbox (Derk-Jan Hartman)
+* MediaWiki theme: Tweak some more border-radii (Bartosz Dziewoński)
+* Widget: Set aria-disabled too in #setDisabled (Derk-Jan Hartman)
+* Twiddle things (Ed Sanders)
+* Add help icon for FieldsetLayout (Moriel Schottlender)
+* PopupButtonWidget: Set aria-haspopup to true (Prateek Saxena)
+* ToggleButtonWidget: Set aria-pressed when changing value (Derk-Jan Hartman)
+* ActionFieldLayout: Add 'nowrap' to the button (Moriel Schottlender)
+* demo: Have multiline text in multiline widgets (Bartosz Dziewoński)
+* Add inline labels to text widgets (Ed Sanders)
+* TextInputWidget: Don't try adjusting size when detached (Bartosz Dziewoński)
+* MediaWiki Theme: Adjust MenuOptionWidget selected state (Bartosz Dziewoński)
+* ToggleWidget: Use aria-checked (Prateek Saxena)
+* MediaWiki theme: Unbreak disabled buttons (Bartosz Dziewoński)
+* MediaWiki theme: Fix background issues with disabled buttons (Roan Kattouw)
+* ButtonOptionWidget: Add the TabIndexedElement mixin (Derk-Jan Hartman)
+* Remove labelPosition check (Ed Sanders)
+* Fix opening/closing animation on windows (Roan Kattouw)
+* Add MenuLayout (Ed Sanders)
+* Add simpler window#updateSize API (Ed Sanders)
+
+## v0.6.3 / 2015-01-14
+* [DEPRECATING CHANGE] LookupInputWidget should no longer be used, instead use LookupElement
+
+* MediaWiki Theme: Adjust toolbar popups' border and shadows (Bartosz Dziewoński)
+* MediaWiki Theme: Don't use 'box-shadow' to produce thin grey lines in dialogs (Bartosz Dziewoński)
+* demo: Switch the default theme from 'Apex' to 'MediaWiki' (Ricordisamoa)
+* Toolbar: Update #initialize docs (Bartosz Dziewoński)
+* Add an ActionFieldLayout (Moriel Schottlender)
+* dialog: Provide a 'larger' size for things for which 'large' isn't enough (James D. Forrester)
+* Synchronize ComboBoxWidget and DropdownWidget styles (Bartosz Dziewoński)
+* Replace old&busted LookupInputWidget with new&hot LookupElement (Bartosz Dziewoński)
+
+## v0.6.2 / 2015-01-09
+* WindowManager#removeWindows: Documentation fix (Ed Sanders)
+* Clear windows when destroying window manager (Ed Sanders)
+* MediaWiki theme: Slightly reduce size of indicator arrows (Ed Sanders)
+* MediaWiki Theme: Remove text-shadow on button (Prateek Saxena)
+* MediaWiki Theme: Fix focus state for buttons (Prateek Saxena)
+* MediaWiki Theme: Fix disabled state of buttons (Prateek Saxena)
+* MediaWiki Theme: Fix overlap between hover and active states (Prateek Saxena)
+* Make @anchor-size a less variable and calculate borders from it (Ed Sanders)
+* PHP LabelElement: Actually allow non-plaintext labels (Bartosz Dziewoński)
+* MediaWiki Theme: Add state change transition to checkbox (Prateek Saxena)
+* Synchronize @abstract class annotations between PHP and JS (Bartosz Dziewoński)
+* Add 'lock' icon (Trevor Parscal)
+* Don't test abstract classes (Bartosz Dziewoński)
+* Element: Add support for 'id' config option (Bartosz Dziewoński)
+* testsuitegenerator.rb: Handle inheritance chains (Bartosz Dziewoński)
+* TextInputWidget: Add support for 'autofocus' config option (Bartosz Dziewoński)
+* tests: Don't overwrite 'id' attribute (Bartosz Dziewoński)
+
+## v0.6.1 / 2015-01-05
+* Remove use of Math.round() for offset and position pixel values (Bartosz Dziewoński)
+* Update JSPHP-suite.json (Bartosz Dziewoński)
+* ButtonElement: Inherit all 'font' styles, not only 'font-family' (Bartosz Dziewoński)
+* FieldsetLayout: Shrink size of label and bump the weight to compensate (James D. Forrester)
+* IndicatorElement: Fix 'indicatorTitle' config option (Bartosz Dziewoński)
+* Error: Unmark as @abstract (Bartosz Dziewoński)
+* build: Update various devDependencies (James D. Forrester)
+* readme: Update badges (Timo Tijhof)
+* readme: No need to put the same heading in twice (James D. Forrester)
+
+## v0.6.0 / 2014-12-16
+* [BREAKING CHANGE] PopupToolGroup and friends: Pay off technical debt (Bartosz Dziewoński)
+* ButtonGroupWidget: Remove weird margin-bottom: -1px; from theme styles (Bartosz Dziewoński)
+* Prevent parent window scroll in modal mode using overflow hidden (Ed Sanders)
+* MediaWiki theme: RadioInputWidget tweaks (Bartosz Dziewoński)
+* ClippableElement: Handle clipping with left edge (Bartosz Dziewoński)
+* Sprinkle some child selectors around in BookletLayout styles (Roan Kattouw)
+
+## v0.5.0 / 2014-12-12
+* [BREAKING CHANGE] FieldLayout: Handle 'inline' alignment better (Bartosz Dziewoński)
+* [BREAKING CHANGE] Split primary flag into primary and progressive (Trevor Parscal)
+* [BREAKING CHANGE] CheckboxInputWidget: Allow setting HTML 'value' attribute (Bartosz Dziewoński)
+* MediaWiki theme: checkbox: Fix states according to spec (Prateek Saxena)
+* MediaWiki theme: Add radio buttons (Prateek Saxena)
+* MediaWiki theme: Use gray instead of blue for select and highlight (Trevor Parscal)
+* MediaWiki theme: Copy .theme-oo-ui-outline{Controls,Option}Widget from Apex (Bartosz Dziewoński)
+* MediaWiki theme: Add thematic border to the bottom of toolbars (Bartosz Dziewoński)
+* MediaWiki theme: Extract @active-color variable (Bartosz Dziewoński)
+* MediaWiki theme: Add hover state to listToolGroup (Trevor Parscal)
+* MediaWiki theme: Add state transition to radio buttons (Prateek Saxena)
+* MediaWiki theme: Make button sizes match Apex (Trevor Parscal)
+* MediaWiki theme: Improve search widget styling (Trevor Parscal)
+* build: Use String#slice instead of discouraged String#substr (Timo Tijhof)
+* Element.getClosestScrollableContainer: Use 'body' or 'documentElement' based on browser (Prateek Saxena)
+* testsuitegenerator: Actually filter out non-unique combinations (Bartosz Dziewoński)
+* Fix primary button description text (Niklas Laxström)
+* Give non-isolated windows a tabIndex for selection holding (Ed Sanders)
+* Call .off() correctly in setButtonElement() (Roan Kattouw)
+* RadioInputWidget: Remove documentation lies (Bartosz Dziewoński)
+* Don't set line-height of unset button labels (Bartosz Dziewoński)
+* Temporarily remove position:absolute on body when resizing (Ed Sanders)
+* Kill the escape keydown event after handling a window close (Ed Sanders)
+* PopupWidget: Remove box-shadow rule that generates invisible shadow (Bartosz Dziewoński)
+* ClippableElement: 7 is a better number than 10 (Bartosz Dziewoński)
+* FieldLayout: In styles, don't assume that label is given (Bartosz Dziewoński)
+* TextInputWidget: Set vertical-align: middle, like buttons (Bartosz Dziewoński)
+* FieldLayout: Synchronise PHP with JS (Bartosz Dziewoński)
+* FieldLayout: Use <label> for this.$body, not this.$element (Bartosz Dziewoński)
+* Account for <html> rather than <body> being the scrollable root in Chrome (Bartosz Dziewoński)
+* GridLayout: Don't round to 1% (Bartosz Dziewoński)
+* README.md: Drop localisation update auto-commits from release notes (James D. Forrester)
+* README.md: Point to Phabricator, not Bugzilla (James D. Forrester)
+
+## v0.4.0 / 2014-12-05
+* [BREAKING CHANGE] Remove deprecated Element#onDOMEvent and #offDOMEvent (Bartosz Dziewoński)
+* [BREAKING CHANGE] Make a number of Element getters static (Bartosz Dziewoński)
+* [BREAKING CHANGE] Rename BookletLayout#getPageName → #getCurrentPageName (Bartosz Dziewoński)
+* demo: Don't put buttons in a FieldsetLayout without FieldLayouts around them (Bartosz Dziewoński)
+* IconElement: Add missing #getIconTitle (Bartosz Dziewoński)
+* SelectWidget: Rewrite #getRelativeSelectableItem (Bartosz Dziewoński)
+* Follow-up I859ff276e: Add cursor files to repo (Trevor Parscal)
+
+## v0.3.0 / 2014-12-04
+* [BREAKING CHANGE] ButtonWidget: Don't default 'target' to 'blank' (Bartosz Dziewoński)
+* InputWidget: Update DOM value before firing 'change' event (Bartosz Dziewoński)
+* TextInputWidget: Reuse a single clone instead of appending and removing new ones (Prateek Saxena)
+* build: Have grunt watch run 'quick-build' instead of 'build' (Prateek Saxena)
+* MediaWiki Theme: Reduce indentation in theme-oo-ui-checkboxInputWidget (Prateek Saxena)
+* Adding DraggableGroupElement and DraggableElement mixins (Moriel Schottlender)
+* Remove window even if closing promise rejects (Ed Sanders)
+* Fix lies in documentation (Trevor Parscal)
+
+## v0.2.4 / 2014-12-02
+* TextInputWidget: Use .css( propertyName, value ) instead of .css( properties) for single property (Prateek Saxena)
+* TextInputWidget: Stop adjustSize if the value of the textarea is the same (Prateek Saxena)
+* Window: Avoid height flickering when resizing dialogs (Bartosz Dziewoński)
+* MessageDialog: Fit actions again when the dialog is resized (Bartosz Dziewoński)
+
+## v0.2.3 / 2014-11-26
+* Dialog: Only handle escape events when open (Alex Monk)
+* Pass original event with TextInputWidget#enter (Ed Sanders)
+* Add missing documentation to ToolFactory (Ed Sanders)
+* BookletLayout: Make #focus not crash when there are zero pages or when there is no outline (Roan Kattouw)
+* Window: Disable transitions when changing window height to calculate content height (Bartosz Dziewoński)
+* MessageDialog: Add Firefox hack for scrollbars when sizing dialogs (Bartosz Dziewoński)
+* Fix RadioOptionWidget demos (Trevor Parscal)
+* RadioOptionWidget: Remove lies from documentation (Trevor Parscal)
+* RadioOptionWidget: Increase rule specificity to match OptionWidget (Bartosz Dziewoński)
+* MessageDialog: Actually correctly calculate and set height (Bartosz Dziewoński)
+
+## v0.2.2 / 2014-11-25
+* LabelWidget: Add missing documentation for input configuration option (Ed Sanders)
+* MessageDialog: Fit actions after updating window size, not before (Bartosz Dziewoński)
+* MessageDialog: Use the right superclass (Bartosz Dziewoński)
+* ProcessDialog, MessageDialog: Support iconed actions (Bartosz Dziewoński)
+* Remove padding from undecorated option widgets (Ed Sanders)
+* build: Add .npmignore (Timo Tijhof)
+
+## v0.2.1 / 2014-11-24
+* Start the window opening transition before ready, not after (Roan Kattouw)
+* Add focus method to BookletLayout (Roan Kattouw)
+* Add missing History.md file now we're a proper repo (James D. Forrester)
+* README.md: Update introduction, badges, advice (James D. Forrester)
+* LabelElement: Kill inline styles (Bartosz Dziewoński)
+* composer: Rename package to 'oojs-ui' and require php 5.3.3 (Timo Tijhof)
+
+## v0.2.0 / 2014-11-17
+* First versioned release
+
+## v0.1.0 / 2013-11-13
+* Initial export of repo
diff --git a/vendor/oojs/oojs-ui/LICENSE-MIT b/vendor/oojs/oojs-ui/LICENSE-MIT
new file mode 100644
index 00000000..1eef0125
--- /dev/null
+++ b/vendor/oojs/oojs-ui/LICENSE-MIT
@@ -0,0 +1,20 @@
+Copyright 2011-2015 OOjs Team and other contributors.
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/oojs/oojs-ui/README.md b/vendor/oojs/oojs-ui/README.md
new file mode 100644
index 00000000..e537752e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/README.md
@@ -0,0 +1,79 @@
+[![npm](https://img.shields.io/npm/v/oojs-ui.svg?style=flat)](https://www.npmjs.com/package/oojs-ui) [![Packagist](https://img.shields.io/packagist/v/oojs/oojs-ui.svg?style=flat)](https://packagist.org/packages/oojs/oojs-ui) [![David](https://img.shields.io/david/dev/wikimedia/oojs-ui.svg?style=flat)](https://david-dm.org/wikimedia/oojs-ui#info=devDependencies)
+
+OOjs UI
+=================
+
+OOjs UI is a modern JavaScript UI toolkit for browsers. It provides a library of common widgets, layouts and windows that are ready to use, as well as many foundational classes for constructing custom user interfaces. The library was originally created for use by [VisualEditor](https://www.mediawiki.org/wiki/VisualEditor), which uses it for its entire user interface, and is now completely independent, and more useful and convenient for other use cases.
+
+Quick start
+----------
+
+This library is available as an [npm](https://npmjs.org/) package! Install it right away:
+<pre lang="bash">
+npm install oojs-ui
+</pre>
+
+If you don't want to use npm, you can:
+
+1. Clone the repo, `git clone https://git.wikimedia.org/git/oojs/ui.git`.
+
+1. Install Grunt command-line utility:<br/>`$ npm install -g grunt-cli`
+
+1. Install dev dependencies and build the distribution files:<br/>`$ npm install`
+
+1. You can now copy the distribution files from the dist directory into your project.
+
+
+Versioning
+----------
+
+We use the Semantic Versioning guidelines as much as possible.
+
+Releases will be numbered in the following format:
+
+`<major>.<minor>.<patch>`
+
+For more information on SemVer, please visit http://semver.org/.
+
+
+Issue tracker
+-------------
+
+Found a bug or missing feature? Please report it in the [issue tracker](https://phabricator.wikimedia.org/maniphest/task/create/?projects=PHID-PROJ-dgmoevjqeqlerleqzzx5)!
+
+
+Release
+----------
+
+Release process:
+<pre lang="bash">
+$ cd path/to/oojs-ui/
+$ git remote update
+$ git checkout -B release -t origin/master
+
+# Ensure tests pass
+$ npm install && npm test
+
+# Avoid using "npm version patch" because that creates
+# both a commit and a tag, and we shouldn't tag until after
+# the commit is merged.
+
+# Update release notes
+# Copy the resulting list into a new section on History.md
+$ git log --format='* %s (%aN)' --no-merges --reverse v$(node -e 'console.log(require("./package.json").version);')...HEAD | grep -v "Localisation updates from" | sort
+$ edit History.md
+
+# Update the version number
+$ edit package.json
+
+$ git add -p
+$ git commit -m "Tag vX.X.X"
+$ git review
+
+# After merging:
+$ git remote update
+$ git checkout origin/master
+$ git tag "vX.X.X"
+$ git push --tags
+$ npm publish
+</pre>
diff --git a/vendor/oojs/oojs-ui/bin/doccomparer.rb b/vendor/oojs/oojs-ui/bin/doccomparer.rb
new file mode 100644
index 00000000..cd3623df
--- /dev/null
+++ b/vendor/oojs/oojs-ui/bin/doccomparer.rb
@@ -0,0 +1,165 @@
+require 'pp'
+require_relative 'docparser'
+
+# convert [ {name: 'foo'}, … ] to { foo: {name: 'foo'}, … }
+def reindex arg
+ if arg.is_a?(Array) && arg.all?{|v| v.is_a? Hash }
+ Hash[ arg.map{|v| [ v[:name], reindex(v) ] } ]
+ elsif arg.is_a? Hash
+ arg.each_pair{|k, v| arg[k] = reindex(v) }
+ else
+ arg
+ end
+end
+
+def indent text, tabs
+ text == '' ? text : text.gsub(/^/, ' ' * tabs)
+end
+
+# whitespace-insensitive strings
+def canonicalize value
+ if value.is_a? String
+ value.strip.gsub(/\s+/, ' ')
+ elsif value.is_a? Array
+ value.map{|v| canonicalize v }
+ elsif value.is_a? Hash
+ value.each_pair{|k, v| value[k] = canonicalize v }
+ else
+ value
+ end
+end
+
+def sanitize_description text
+ cleanup_class_name(text)
+ .gsub('null and undefined', 'null')
+ .gsub('undefined and null', 'null')
+ .gsub('array()', '[]')
+ .gsub('jQuery|string|Function', 'string')
+ .gsub('jQuery', 'Tag')
+ .gsub('string|Function', 'string')
+ .gsub('object', 'array')
+ .gsub(/#(\w+)/, '\1()')
+ .gsub(/Object\.<.+?>/, 'array')
+end
+
+def smart_compare_process val, type
+ val[:description] = sanitize_description val[:description]
+
+ case type
+ when :class
+ val = val.dup
+ val[:mixins].delete 'OO.EventEmitter' # JS only
+ val[:mixins].delete 'PendingElement' # JS only
+ val.delete :parent if val[:parent] == 'ElementMixin' # PHP only
+ val.delete :methods
+ val.delete :properties
+ val.delete :events
+
+ when :method
+ if val[:name] == '#constructor'
+ val[:params].delete 'element' # PHP only
+ end
+ val[:config].each_pair{|_k, v|
+ default = v.delete :default
+ v[:description] << " (default: #{default})" if default
+ v[:description] = sanitize_description v[:description]
+ v[:type] = sanitize_description v[:type]
+ }
+ val[:params].each_pair{|_k, v|
+ default = v.delete :default
+ v[:description] << " (default: #{default})" if default
+ v[:description] = sanitize_description v[:description]
+ v[:type] = sanitize_description v[:type]
+ }
+ if val[:return]
+ val[:return][:description] = sanitize_description val[:return][:description]
+ val[:return][:type] = sanitize_description val[:return][:type]
+ end
+
+ when :property
+ val[:description] = sanitize_description val[:description]
+ val[:type] = sanitize_description val[:type]
+
+ end
+ val
+end
+
+def smart_compare a, b, a_name, b_name, type
+ a = smart_compare_process a, type
+ b = smart_compare_process b, type
+ compare_hash a, b, a_name, b_name
+end
+
+def smart_compare_methods a, b, a_name, b_name
+ smart_compare a, b, a_name, b_name, :method
+end
+
+def smart_compare_properties a, b, a_name, b_name
+ smart_compare a, b, a_name, b_name, :property
+end
+
+def compare_hash a, b, a_name, b_name, nested=:compare_hash
+ keys = (a ? a.keys : []) + (b ? b.keys : [])
+ out = keys.to_a.sort.uniq.map do |key|
+ a_val = a ? canonicalize(a[key]) : nil
+ b_val = b ? canonicalize(b[key]) : nil
+
+ if [a_val, b_val] == [{}, []] || [a_val, b_val] == [[], {}]
+ a_val, b_val = {}, {}
+ end
+
+ if a_val.is_a?(Hash) && b_val.is_a?(Hash)
+ comparison_result = indent method(nested).call(a_val, b_val, a_name, b_name), 2
+ if comparison_result.strip == ''
+ "#{key}: match" if $VERBOSE
+ else
+ "#{key}: MISMATCH\n#{comparison_result}"
+ end
+ else
+ if a_val == b_val
+ "#{key}: match" if $VERBOSE
+ elsif a_val.nil?
+ "#{key}: #{a_name} missing"
+ elsif b_val.nil?
+ "#{key}: #{b_name} missing"
+ else
+ "#{key}: MISMATCH\n #{a_name}: #{a_val.inspect}\n #{b_name}: #{b_val.inspect}"
+ end
+ end
+ end
+ out.compact.join "\n"
+end
+
+if ARGV.empty? || ARGV == ['-h'] || ARGV == ['--help']
+ $stderr.puts "usage: ruby [-v] #{$PROGRAM_NAME} <dirA> <dirB> <nameA> <nameB>"
+ $stderr.puts " ruby #{$PROGRAM_NAME} src php JS PHP > compare.txt"
+else
+ dir_a, dir_b, name_a, name_b = ARGV
+
+ js = parse_any_path dir_a
+ php = parse_any_path dir_b
+
+ js = reindex js
+ php = reindex php
+
+ (js.keys + php.keys).sort.uniq.each do |class_name|
+ where = [js.key?(class_name) ? name_a : nil, php.key?(class_name) ? name_b : nil].compact
+ puts "\n#{class_name}: #{where.join '/'}" if $VERBOSE || where.length == 2
+
+ if where.length == 2
+ data = {
+ 'Basic:' =>
+ smart_compare(js[class_name], php[class_name], name_a, name_b, :class),
+ 'Methods:' =>
+ compare_hash(js[class_name][:methods], php[class_name][:methods], name_a, name_b, :smart_compare_methods),
+ 'Properties:' =>
+ compare_hash(js[class_name][:properties], php[class_name][:properties], name_a, name_b, :smart_compare_properties),
+ }
+ data = data
+ .select{|_k, v| v != ''}
+ .map{|k, v| "#{k}\n#{indent v, 2}" }
+ .join("\n")
+ puts indent data, 2
+ end
+ end
+end
diff --git a/vendor/oojs/oojs-ui/bin/docparser.rb b/vendor/oojs/oojs-ui/bin/docparser.rb
new file mode 100644
index 00000000..9f58549b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/bin/docparser.rb
@@ -0,0 +1,243 @@
+require 'pp'
+require 'json'
+
+def parse_dir dirname
+ Dir.entries(dirname).map{|filename|
+ if filename == '.' || filename == '..'
+ nil
+ else
+ parse_any_path "#{dirname}/#{filename}"
+ end
+ }.compact.inject(:+)
+end
+
+def cleanup_class_name class_name
+ class_name.sub(/OO\.ui\./, '')
+end
+
+def parse_file filename
+ if filename !~ /\.(php|js)$/
+ return nil
+ end
+ filetype = filename[/\.(php|js)$/, 1].to_sym
+
+ text = File.read filename, encoding: 'utf-8'
+
+ # ewwww
+ # some docblocks are missing and we really need them
+ text = text.sub(/(?<!\*\/\n)^class/, "/**\n*/\nclass")
+ # text = text.sub('public static $targetPropertyName', "/**\n*/\npublic static $targetPropertyName")
+
+ # find all documentation blocks, together with the following line (unless it contains another docblock)
+ docblocks = text.scan(/\/\*\*[\s\S]+?\*\/\n[ \t]*(?:(?=\/\*\*)|.*)/)
+
+ current_class = nil
+ output = []
+ previous_item = {} # dummy
+
+ docblocks.each{|d|
+ kind = nil
+ previous_item = data = {
+ name: nil,
+ description: '',
+ parent: nil,
+ mixins: [],
+ methods: [],
+ properties: [],
+ events: [],
+ params: [],
+ config: [],
+ visibility: :public,
+ type: nil,
+ }
+ valid_for_all = %w[name description].map(&:to_sym)
+ valid_per_kind = {
+ class: valid_for_all + %w[parent mixins methods properties events abstract].map(&:to_sym),
+ method: valid_for_all + %w[params config return visibility static].map(&:to_sym),
+ property: valid_for_all + %w[type static].map(&:to_sym),
+ event: valid_for_all + %w[params].map(&:to_sym),
+ }
+
+ js_class_constructor = false
+ js_class_constructor_desc = ''
+ ignore = false
+
+ comment, code_line = d.split '*/'
+ comment.split("\n").each{|c|
+ next if c.strip == '/**'
+ c.sub!(/^[ \t]*\*[ \t]?/, '') # strip leading *
+
+ m = c.match(/^@(\w+)[ \t]*(.*)/)
+ unless m
+ previous_item[:description] << c + "\n"
+ next
+ end
+
+ keyword, content = m.captures
+
+ # handle JS class/constructor conundrum
+ if keyword == 'class' || keyword == 'constructor'
+ js_class_constructor = true
+ end
+
+ case keyword
+ when 'constructor'
+ # handle JS class/constructor conundrum
+ js_class_constructor_desc = data[:description]
+ data[:description] = ''
+ kind = :method
+ when 'class'
+ kind = :class
+ when 'method'
+ kind = :method
+ when 'property', 'var'
+ kind = :property
+ m = content.match(/^\{?(.+?)\}?( .+)?$/)
+ if m.captures
+ type, description = m.captures
+ data[:type] = type
+ data[:description] = description if description
+ end
+ when 'event'
+ kind = :event
+ data[:name] = content.strip
+ when 'extends'
+ data[:parent] = cleanup_class_name(content.strip)
+ when 'mixins'
+ data[:mixins] << cleanup_class_name(content.strip)
+ when 'param'
+ case filetype
+ when :js
+ type, name, default, description = content.match(/^\{(.+?)\} \[?([\w.$]+?)(?:=(.+?))?\]?( .+)?$/).captures
+ next if type == 'Object' && name == 'config'
+ data[:params] << {name: name, type: cleanup_class_name(type), description: description || '', default: default}
+ previous_item = data[:params][-1]
+ when :php
+ type, name, config, description = content.match(/^(\S+) \&?\$(\w+)(?:\['(\w+)'\])?( .+)?$/).captures
+ next if type == 'array' && name == 'config' && !config
+ if config && name == 'config'
+ data[:config] << {name: config, type: cleanup_class_name(type), description: description || ''}
+ previous_item = data[:config][-1]
+ else
+ data[:params] << {name: name, type: cleanup_class_name(type), description: description || ''}
+ previous_item = data[:params][-1]
+ end
+ end
+ when 'cfg' # JS only
+ type, name, default, description = content.match(/^\{(.+?)\} \[?([\w.$]+?)(?:=(.+?))?\]?( .+)?$/).captures
+ data[:config] << {name: name, type: cleanup_class_name(type), description: description || '', default: default}
+ previous_item = data[:config][-1]
+ when 'return'
+ case filetype
+ when :js
+ type, description = content.match(/^\{(.+?)\}( .+)?$/).captures
+ data[:return] = {type: cleanup_class_name(type), description: description || ''}
+ previous_item = data[:return]
+ when :php
+ type, description = content.match(/^(\S+)( .+)?$/).captures
+ data[:return] = {type: cleanup_class_name(type), description: description || ''}
+ previous_item = data[:return]
+ end
+ when 'private'
+ data[:visibility] = :private
+ when 'protected'
+ data[:visibility] = :protected
+ when 'static'
+ data[:static] = true
+ when 'abstract'
+ data[:abstract] = true
+ when 'ignore'
+ ignore = true
+ when 'inheritable', 'deprecated', 'singleton', 'throws',
+ 'chainable', 'fires', 'localdoc', 'inheritdoc', 'member',
+ 'see'
+ # skip
+ else
+ fail "unrecognized keyword: #{keyword}"
+ end
+ }
+
+ next if ignore
+
+ if code_line && code_line.strip != ''
+ case filetype
+ when :js
+ m = code_line.match(/(?:(static|prototype)\.)?(\w+) =/)
+ kind_, name = m.captures
+ data[:static] = true if kind_ == 'static'
+ kind = {'static' => :property, 'prototype' => :method}[ kind_.strip ] if kind_ && !kind
+ data[:name] = cleanup_class_name(name)
+ when :php
+ m = code_line.match(/
+ \s*
+ (?:(public|protected|private)\s)?
+ (?:(static)\s)?(function\s|class\s|\$)
+ (\w+)
+ (?:\sextends\s(\w+))?
+ /x)
+ visibility, static, kind_, name, parent = m.captures
+ kind = {'$' => :property, 'function' => :method, 'class' => :class}[ kind_.strip ]
+ data[:visibility] = {'private' => :private, 'protected' => :protected, 'public' => :public}[ visibility ] || :public
+ data[:static] = true if static
+ data[:parent] = cleanup_class_name(parent) if parent
+ data[:name] = cleanup_class_name(name)
+ end
+ end
+
+ # handle JS class/constructor conundrum
+ if kind == :class || js_class_constructor
+ if current_class
+ output << current_class
+ end
+ current_class = data.select{|k, _v| valid_per_kind[:class].include? k }
+ current_class[:description] = js_class_constructor_desc if js_class_constructor_desc != ''
+ previous_item = current_class
+ end
+
+ # standardize
+ if data[:name] == '__construct' || js_class_constructor
+ data[:name] = '#constructor'
+ end
+
+ # put into the current class
+ if kind && kind != :class
+ keys = {
+ method: :methods,
+ property: :properties,
+ event: :events,
+ }
+ current_class[keys[kind]] << data.select{|k, _v| valid_per_kind[kind].include? k }
+ previous_item = current_class[keys[kind]]
+ end
+ }
+
+ # this is evil, assumes we only have one class in a file, but we'd need a proper parser to do it better
+ if current_class
+ current_class[:mixins] +=
+ text.scan(/\$this->mixin\( .*?new (\w+)\( \$this/).flatten.map(&method(:cleanup_class_name))
+ end
+
+ output << current_class if current_class
+ output
+end
+
+def parse_any_path path
+ if File.directory? path
+ parse_dir path
+ else
+ parse_file path
+ end
+end
+
+if __FILE__ == $PROGRAM_NAME
+ if ARGV.empty? || ARGV == ['-h'] || ARGV == ['--help']
+ $stderr.puts "usage: ruby #{$PROGRAM_NAME} <files...>"
+ $stderr.puts " ruby #{$PROGRAM_NAME} src > docs-js.json"
+ $stderr.puts " ruby #{$PROGRAM_NAME} php > docs-php.json"
+ else
+ out = JSON.pretty_generate ARGV.map{|a| parse_any_path a }.inject(:+)
+ # ew
+ out = out.gsub(/\{\s+\}/, '{}').gsub(/\[\s+\]/, '[]')
+ puts out
+ end
+end
diff --git a/vendor/oojs/oojs-ui/bin/generate-JSPHP-for-karma.php b/vendor/oojs/oojs-ui/bin/generate-JSPHP-for-karma.php
new file mode 100644
index 00000000..445da65c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/bin/generate-JSPHP-for-karma.php
@@ -0,0 +1,50 @@
+<?php
+
+// Quick and dirty autoloader to make it possible to run without Composer.
+spl_autoload_register( function ( $class ) {
+ $class = preg_replace( '/^OOUI\\\\/', '', $class );
+ foreach ( array( 'elements', 'layouts', 'themes', 'widgets', '.' ) as $dir ) {
+ $path = "../php/$dir/$class.php";
+ if ( file_exists( $path ) ) {
+ require_once $path;
+ return;
+ }
+ }
+} );
+
+$testSuiteJSON = file_get_contents( 'JSPHP-suite.json' );
+$testSuite = json_decode( $testSuiteJSON, true );
+$testSuiteOutput = array();
+
+function new_OOUI( $class, $config = array() ) {
+ $class = "OOUI\\" . $class;
+ return new $class( $config );
+}
+function unstub( &$value ) {
+ if ( is_string( $value ) && substr( $value, 0, 13 ) === '_placeholder_' ) {
+ $value = json_decode( substr( $value, 13 ), true );
+ array_walk_recursive( $value['config'], 'unstub' );
+ $value = new_OOUI( $value['class'], $value['config'] );
+ }
+}
+// Keep synchronized with tests/index.php
+$themes = array( 'ApexTheme', 'MediaWikiTheme' );
+foreach ( $themes as $theme ) {
+ OOUI\Theme::setSingleton( new_OOUI( $theme ) );
+ foreach ( $testSuite as $className => $tests ) {
+ foreach ( $tests as $test ) {
+ // Unstub placeholders
+ $config = $test['config'];
+ array_walk_recursive( $config, 'unstub' );
+ $config['infusable'] = true;
+ $instance = new_OOUI( $test['class'], $config );
+ $testSuiteOutput[$theme][$className][] = "$instance";
+ }
+ }
+}
+
+$testSuiteOutputJSON = json_encode( $testSuiteOutput, JSON_PRETTY_PRINT );
+
+echo "var testSuiteConfigs = $testSuiteJSON;\n\n";
+echo "var testSuitePHPOutput = $testSuiteOutputJSON;\n\n";
+echo file_get_contents( 'JSPHP.test.karma.js' );
diff --git a/vendor/oojs/oojs-ui/bin/testsuitegenerator.rb b/vendor/oojs/oojs-ui/bin/testsuitegenerator.rb
new file mode 100644
index 00000000..28ab1a85
--- /dev/null
+++ b/vendor/oojs/oojs-ui/bin/testsuitegenerator.rb
@@ -0,0 +1,146 @@
+require 'pp'
+require_relative 'docparser'
+
+if ARGV.empty? || ARGV == ['-h'] || ARGV == ['--help']
+ $stderr.puts "usage: ruby #{$PROGRAM_NAME} <dirA> <dirB>"
+ $stderr.puts " ruby #{$PROGRAM_NAME} src php > tests/JSPHP-suite.json"
+else
+ dir_a, dir_b = ARGV
+ js = parse_any_path dir_a
+ php = parse_any_path dir_b
+
+ class_names = (js + php).map{|c| c[:name] }.sort.uniq
+
+ tests = []
+ classes = php.select{|c| class_names.include? c[:name] }
+
+ testable_classes = classes
+ .reject{|c| c[:abstract] } # can't test abstract classes
+ .reject{|c| !c[:parent] || c[:parent] == 'ElementMixin' || c[:parent] == 'Theme' } # can't test abstract
+ .reject{|c| %w[Element Widget Layout Theme].include? c[:name] } # no toplevel
+ .reject{|c| c[:name] == 'DropdownInputWidget' } # different PHP and JS implementations
+
+ # values to test for each type
+ expandos = {
+ 'null' => [nil],
+ 'number' => [0, -1, 300],
+ 'boolean' => [true, false],
+ 'string' => ['Foo bar', '<b>HTML?</b>'],
+ }
+
+ # values to test for names
+ sensible_values = {
+ 'href' => ['http://example.com/'],
+ ['TextInputWidget', 'type'] => %w[text password],
+ ['ButtonInputWidget', 'type'] => %w[button input],
+ ['FieldLayout', 'help'] => true, # different PHP and JS implementations
+ ['FieldsetLayout', 'help'] => true, # different PHP and JS implementations
+ 'type' => %w[text button],
+ 'method' => %w[GET POST],
+ 'action' => [],
+ 'enctype' => true,
+ 'target' => ['_blank'],
+ 'accessKey' => ['k'],
+ 'name' => true,
+ 'autofocus' => true, # usually makes no sense in JS
+ 'tabIndex' => [-1, 0, 100],
+ 'icon' => ['picture'],
+ 'indicator' => ['down'],
+ 'flags' => %w[constructive],
+ 'label' => expandos['string'] + ['', ' '],
+ # these are defined by Element and would bloat the tests
+ 'classes' => true,
+ 'id' => true,
+ 'content' => true,
+ 'text' => true,
+ }
+
+ find_class = lambda do |klass|
+ return classes.find{|c| c[:name] == klass }
+ end
+
+ expand_types_to_values = lambda do |types|
+ return types.map{|t|
+ as_array = true if t.sub! '[]', ''
+ t = 'ButtonWidget' if t == 'Widget' # Widget is not "testable", use a subclass
+ if expandos[t]
+ # Primitive. Run tests with the provided values.
+ vals = expandos[t]
+ elsif testable_classes.find{|c| c[:name] == t }
+ # OOUI object. Test suite will instantiate one and run the test with it.
+ params = find_class.call(t)[:methods][0][:params] || []
+ config = params.map{|config_option|
+ types = config_option[:type].split '|'
+ values = expand_types_to_values.call(types)
+ { config_option[:name] => values[0] }
+ }
+ vals = [ '_placeholder_' + {
+ class: t,
+ config: config.inject({}, :merge)
+ }.to_json ]
+ else
+ # We don't know how to test this. The empty value will result in no
+ # tests being generated for this combination of config values.
+ vals = []
+ end
+ as_array ? vals.map{|v| [v] } : vals
+ }.inject(:+)
+ end
+
+ find_config_sources = lambda do |klass_name|
+ return [] unless klass_name
+ klass_names = [klass_name]
+ while klass_name
+ klass = find_class.call(klass_name)
+ break unless klass
+ klass_names +=
+ find_config_sources.call(klass[:parent]) +
+ klass[:mixins].map(&find_config_sources).flatten
+ klass_name = klass[:parent]
+ end
+ return klass_names.uniq
+ end
+
+ testable_classes.each do |klass|
+ config_sources = find_config_sources.call(klass[:name])
+ .map{|c| find_class.call(c)[:methods][0] }
+ config = config_sources.map{|c| c[:config] }.compact.inject(:+)
+ required_config = klass[:methods][0][:params] || []
+
+ # generate every possible configuration of configuration option sets
+ maxlength = [config.length, 2].min
+ config_combinations = (0..maxlength).map{|l| config.combination(l).to_a }.inject(:+)
+ # for each set, generate all possible values to use based on option's type
+ config_combinations = config_combinations.map{|config_comb|
+ config_comb += required_config
+ expanded = config_comb.map{|config_option|
+ types = config_option[:type].split '|'
+ sensible = sensible_values[ [ klass[:name], config_option[:name] ] ] ||
+ sensible_values[ config_option[:name] ]
+ if sensible == true
+ [] # the empty value will result in no tests being generated
+ else
+ values = sensible || expand_types_to_values.call(types)
+ values.map{|v| config_option.dup.merge(value: v) } + [nil]
+ end
+ }
+ expanded.length > 0 ? expanded[0].product(*expanded[1..-1]) : []
+ }.inject(:concat).map(&:compact).uniq
+
+ # really require the required ones
+ config_combinations = config_combinations.select{|config_comb|
+ required_config.all?{|r| config_comb.find{|c| c[:name] == r[:name] } }
+ }
+
+ config_combinations.each do |config_comb|
+ tests << {
+ class: klass[:name],
+ config: Hash[ config_comb.map{|c| [ c[:name], c[:value] ] } ]
+ }
+ end
+ end
+
+ tests = tests.group_by{|t| t[:class] }
+
+ puts JSON.pretty_generate tests
+end
diff --git a/vendor/oojs/oojs-ui/build/banner.txt b/vendor/oojs/oojs-ui/build/banner.txt
new file mode 100644
index 00000000..f6926e8f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/build/banner.txt
@@ -0,0 +1,10 @@
+/*!
+ * OOjs UI v<%= pkg.version %>
+ * https://www.mediawiki.org/wiki/OOjs_UI
+ *
+ * Copyright 2011–<%= grunt.template.today("yyyy") %> OOjs Team and other contributors.
+ * Released under the MIT license
+ * http://oojs.mit-license.org
+ *
+ * Date: <%= grunt.template.today("isoUtcDateTime") %>
+ */
diff --git a/vendor/oojs/oojs-ui/build/modules.json b/vendor/oojs/oojs-ui/build/modules.json
new file mode 100644
index 00000000..5aa773ff
--- /dev/null
+++ b/vendor/oojs/oojs-ui/build/modules.json
@@ -0,0 +1,240 @@
+{
+ "oojs-ui": {
+ "scripts": [
+ "src/intro.js.txt",
+ "src/core.js",
+
+ "src/elements/PendingElement.js",
+
+ "src/ActionSet.js",
+ "src/Element.js",
+ "src/Layout.js",
+ "src/Widget.js",
+ "src/Window.js",
+ "src/Dialog.js",
+ "src/WindowManager.js",
+ "src/Error.js",
+ "src/HtmlSnippet.js",
+ "src/Process.js",
+ "src/ToolFactory.js",
+ "src/ToolGroupFactory.js",
+ "src/Theme.js",
+
+ "src/elements/TabIndexedElement.js",
+ "src/elements/ButtonElement.js",
+ "src/elements/GroupElement.js",
+ "src/elements/DraggableElement.js",
+ "src/elements/DraggableGroupElement.js",
+ "src/elements/IconElement.js",
+ "src/elements/IndicatorElement.js",
+ "src/elements/LabelElement.js",
+ "src/elements/LookupElement.js",
+ "src/elements/PopupElement.js",
+ "src/elements/FlaggedElement.js",
+ "src/elements/TitledElement.js",
+ "src/elements/ClippableElement.js",
+
+ "src/Tool.js",
+ "src/Toolbar.js",
+ "src/ToolGroup.js",
+
+ "src/dialogs/MessageDialog.js",
+ "src/dialogs/ProcessDialog.js",
+
+ "src/layouts/FieldLayout.js",
+ "src/layouts/ActionFieldLayout.js",
+ "src/layouts/FieldsetLayout.js",
+ "src/layouts/FormLayout.js",
+ "src/layouts/MenuLayout.js",
+ "src/layouts/BookletLayout.js",
+ "src/layouts/IndexLayout.js",
+ "src/layouts/PanelLayout.js",
+ "src/layouts/CardLayout.js",
+ "src/layouts/PageLayout.js",
+ "src/layouts/StackLayout.js",
+
+ "src/toolgroups/BarToolGroup.js",
+ "src/toolgroups/PopupToolGroup.js",
+ "src/toolgroups/ListToolGroup.js",
+ "src/toolgroups/MenuToolGroup.js",
+
+ "src/tools/PopupTool.js",
+ "src/tools/ToolGroupTool.js",
+
+ "src/widgets/GroupWidget.js",
+ "src/widgets/ItemWidget.js",
+ "src/widgets/OutlineControlsWidget.js",
+ "src/widgets/ToggleWidget.js",
+
+ "src/widgets/ButtonGroupWidget.js",
+ "src/widgets/ButtonWidget.js",
+ "src/widgets/ActionWidget.js",
+ "src/widgets/PopupButtonWidget.js",
+ "src/widgets/ToggleButtonWidget.js",
+ "src/widgets/DropdownWidget.js",
+ "src/widgets/IconWidget.js",
+ "src/widgets/IndicatorWidget.js",
+ "src/widgets/InputWidget.js",
+ "src/widgets/ButtonInputWidget.js",
+ "src/widgets/CheckboxInputWidget.js",
+ "src/widgets/DropdownInputWidget.js",
+ "src/widgets/RadioInputWidget.js",
+ "src/widgets/TextInputWidget.js",
+ "src/widgets/ComboBoxWidget.js",
+ "src/widgets/LabelWidget.js",
+ "src/widgets/OptionWidget.js",
+ "src/widgets/DecoratedOptionWidget.js",
+ "src/widgets/ButtonOptionWidget.js",
+ "src/widgets/RadioOptionWidget.js",
+ "src/widgets/MenuOptionWidget.js",
+ "src/widgets/MenuSectionOptionWidget.js",
+ "src/widgets/OutlineOptionWidget.js",
+ "src/widgets/TabOptionWidget.js",
+ "src/widgets/PopupWidget.js",
+ "src/widgets/ProgressBarWidget.js",
+ "src/widgets/SearchWidget.js",
+ "src/widgets/SelectWidget.js",
+ "src/widgets/ButtonSelectWidget.js",
+ "src/widgets/RadioSelectWidget.js",
+ "src/widgets/MenuSelectWidget.js",
+ "src/widgets/TextInputMenuSelectWidget.js",
+ "src/widgets/OutlineSelectWidget.js",
+ "src/widgets/TabSelectWidget.js",
+ "src/widgets/ToggleSwitchWidget.js",
+
+ "src/outro.js.txt"
+ ]
+ },
+ "oojs-ui-apex": {
+ "scripts": [
+ "src/themes/apex/ApexTheme.js"
+ ],
+ "styles": [
+ "src/themes/apex/core.less",
+ "src/themes/apex/icons.json",
+ "src/themes/apex/indicators.json",
+ "src/themes/apex/textures.json"
+ ]
+ },
+ "oojs-ui-apex-noimages": {
+ "styles": [
+ "src/themes/apex/core.less"
+ ]
+ },
+ "oojs-ui-apex-icons-movement": {
+ "styles": [
+ "src/themes/apex/icons-movement.json"
+ ]
+ },
+ "oojs-ui-apex-icons-moderation": {
+ "styles": [
+ "src/themes/apex/icons-moderation.json"
+ ]
+ },
+ "oojs-ui-apex-icons-editing-core": {
+ "styles": [
+ "src/themes/apex/icons-editing-core.json"
+ ]
+ },
+ "oojs-ui-apex-icons-editing-styling": {
+ "styles": [
+ "src/themes/apex/icons-editing-styling.json"
+ ]
+ },
+ "oojs-ui-apex-icons-editing-list": {
+ "styles": [
+ "src/themes/apex/icons-editing-list.json"
+ ]
+ },
+ "oojs-ui-apex-icons-editing-advanced": {
+ "styles": [
+ "src/themes/apex/icons-editing-advanced.json"
+ ]
+ },
+ "oojs-ui-mediawiki": {
+ "scripts": [
+ "src/themes/mediawiki/MediaWikiTheme.js"
+ ],
+ "styles": [
+ "src/themes/mediawiki/core.less",
+ "src/themes/mediawiki/icons.json",
+ "src/themes/mediawiki/indicators.json",
+ "src/themes/mediawiki/textures.json"
+ ]
+ },
+ "oojs-ui-mediawiki-noimages": {
+ "styles": [
+ "src/themes/mediawiki/core.less"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-movement": {
+ "styles": [
+ "src/themes/mediawiki/icons-movement.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-content": {
+ "styles": [
+ "src/themes/mediawiki/icons-content.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-alerts": {
+ "styles": [
+ "src/themes/mediawiki/icons-alerts.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-interactions": {
+ "styles": [
+ "src/themes/mediawiki/icons-interactions.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-moderation": {
+ "styles": [
+ "src/themes/mediawiki/icons-moderation.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-editing-core": {
+ "styles": [
+ "src/themes/mediawiki/icons-editing-core.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-editing-styling": {
+ "styles": [
+ "src/themes/mediawiki/icons-editing-styling.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-editing-list": {
+ "styles": [
+ "src/themes/mediawiki/icons-editing-list.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-editing-advanced": {
+ "styles": [
+ "src/themes/mediawiki/icons-editing-advanced.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-media": {
+ "styles": [
+ "src/themes/mediawiki/icons-media.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-location": {
+ "styles": [
+ "src/themes/mediawiki/icons-location.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-user": {
+ "styles": [
+ "src/themes/mediawiki/icons-user.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-layout": {
+ "styles": [
+ "src/themes/mediawiki/icons-layout.json"
+ ]
+ },
+ "oojs-ui-mediawiki-icons-wikimedia": {
+ "styles": [
+ "src/themes/mediawiki/icons-wikimedia.json"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/build/tasks/colorize-svg.js b/vendor/oojs/oojs-ui/build/tasks/colorize-svg.js
new file mode 100644
index 00000000..0eb421f3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/build/tasks/colorize-svg.js
@@ -0,0 +1,538 @@
+/*!
+ * Colorize SVG files.
+ *
+ * The task currently doesn't use the standard file specifying methods with this.filesSrc.
+ * An option to do it may be added in the future.
+ */
+
+/*jshint node:true */
+
+var Q = require( 'q' ),
+ path = require( 'path' ),
+ asyncTask = require( 'grunt-promise-q' );
+
+module.exports = function ( grunt ) {
+
+ asyncTask.registerMulti(
+ grunt,
+ 'colorizeSvg',
+ 'Generate colored variants of SVG images',
+ function () {
+ var
+ data = this.data,
+ options = this.options(),
+ source = new Source(
+ data.srcDir,
+ options.images,
+ options.variants,
+ {
+ intro: options.intro,
+ prefix: options.prefix,
+ cssPrependPath: data.cssPrependPath,
+ selectorWithoutVariant: options.selectorWithoutVariant || options.selector,
+ selectorWithVariant: options.selectorWithVariant || options.selector
+ }
+ );
+
+ return source.getImageList().generate(
+ new Destination(
+ data.destDir,
+ data.destLessFile || {
+ ltr: path.join( data.destDir, 'images.less' ),
+ rtl: path.join( data.destDir, 'images.rtl.less' )
+ }
+ )
+ ).then( function ( totalFiles ) {
+ grunt.log.writeln( 'Created ' + totalFiles + ' SVG files.' );
+ } );
+ }
+ );
+
+ /* Classes */
+
+ /**
+ * Image source.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {string} path Directory containing source images
+ * @param {Object} images Lists of image configurations
+ * @param {Object} [variants] List of variant configurations, keyed by variant name
+ * @param {Object} [options] Additional options
+ */
+ function Source( path, images, variants, options ) {
+ this.path = path;
+ this.images = images;
+ this.variants = variants || {};
+ this.options = options || {};
+ }
+
+ /**
+ * Get the path to source images directory.
+ *
+ * @return {string} Path
+ */
+ Source.prototype.getPath = function () {
+ return this.path;
+ };
+
+ /**
+ * Get image list.
+ *
+ * @return ImageList Image list
+ */
+ Source.prototype.getImageList = function () {
+ return new ImageList(
+ this.path,
+ new VariantList( this.variants ),
+ this.options,
+ this.images
+ );
+ };
+
+ /**
+ * Destination for images.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {string} path Image path
+ * @param {Object} stylesheetPath Stylesheet file path
+ * @param {string} stylesheetPath.ltr Stylesheet file path, left-to-right
+ * @param {string} stylesheetPath.rtl Stylesheet file path, right-to-left
+ */
+ function Destination( path, stylesheetPath ) {
+ this.path = path;
+ this.stylesheetPath = stylesheetPath;
+ }
+
+ /**
+ * Get image destination directory.
+ *
+ * @return {string} Destination path
+ */
+ Destination.prototype.getPath = function () {
+ return this.path;
+ };
+
+ /**
+ * Get path to file of generated Less stylesheet.
+ *
+ * @param {string} textDirection Text direction to get stylesheet path for, 'ltr' or 'rtl'
+ * @return {string} Destination path
+ */
+ Destination.prototype.getStylesheetPath = function ( textDirection ) {
+ return this.stylesheetPath[ textDirection ];
+ };
+
+ /**
+ * Source image.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {Object} list Image list
+ * @param {string} name Image name
+ * @param {Object} data Image options
+ */
+ function Image( list, name, data ) {
+ this.list = list;
+ this.name = name;
+ this.file = data.file;
+ this.variantNames = ( data.variants || [] )
+ .concat( this.list.getVariants().getGlobalVariantNames() )
+ .filter( function ( variant, index, variants ) {
+ return variants.indexOf( variant ) === index;
+ } );
+ }
+
+ /**
+ * Generate CSS and images.
+ *
+ * @param {Destination} destination Destination
+ * @return {Q.Promise}
+ */
+ Image.prototype.generate = function ( destination ) {
+ // TODO Make configurable
+ function getDeclarations( primary ) {
+ // If 'primary' is not a SVG file, 'fallback' and 'primary' are intentionally the same
+ var fallback = primary.replace( /\.svg$/, '.png' );
+ return '.oo-ui-background-image-svg2(' +
+ '\'' + ( cssPrependPath || '' ) + primary + '\', ' +
+ '\'' + ( cssPrependPath || '' ) + fallback + '\'' +
+ ')';
+ }
+ function variantizeFileName( fileName, variantName ) {
+ if ( variantName ) {
+ return fileName.replace( /\.(\w+)$/, '-' + variantName + '.$1' );
+ }
+ return fileName;
+ }
+
+ var selector, declarations, direction, lang, langSelector,
+ deferred = Q.defer(),
+ file = typeof this.file === 'string' ?
+ { ltr: this.file, rtl: this.file } :
+ { ltr: this.file.ltr || this.file.default, rtl: this.file.rtl || this.file.default },
+ moreLangs = this.file.lang || {},
+ name = this.name,
+ sourcePath = this.list.getPath(),
+ destinationPath = destination.getPath(),
+ variants = this.list.getVariants(),
+ cssClassPrefix = this.list.getCssClassPrefix(),
+ cssSelectors = this.list.getCssSelectors(),
+ cssPrependPath = this.list.options.cssPrependPath,
+ originalSvg = {},
+ rules = {
+ ltr: [],
+ rtl: []
+ },
+ files = {},
+ uncolorizableImages = [],
+ unknownVariants = [];
+
+ // Expand shorthands:
+ // { "en,de,fr": "foo.svg" } → { "en": "foo.svg", "de": "foo.svg", "fr": "foo.svg" }
+ moreLangs = Object.keys( moreLangs ).reduce( function ( langs, langList ) {
+ langList.split( ',' ).forEach( function ( lang ) {
+ langs[ lang ] = moreLangs[ langList ];
+ } );
+ return langs;
+ }, {} );
+
+ // Original
+ selector = cssSelectors.selectorWithoutVariant
+ .replace( /{prefix}/g, cssClassPrefix )
+ .replace( /{name}/g, name )
+ .replace( /{variant}/g, '' );
+
+ for ( direction in file ) {
+ declarations = getDeclarations( file[ direction ] );
+ rules[ direction ].push( selector + ' {\n\t' + declarations + '\n}' );
+
+ originalSvg[ direction ] = grunt.file.read(
+ path.join( sourcePath, file[ direction ] )
+ );
+ files[ path.join( destinationPath, file[ direction ] ) ] = originalSvg[ direction ];
+
+ for ( lang in moreLangs ) {
+ // This will not work for selectors ending in a pseudo-element.
+ langSelector = ':lang(' + lang + ')';
+ declarations = getDeclarations( moreLangs[ lang ] );
+ rules[ direction ].push(
+ '/* @noflip */\n' +
+ selector.replace( /,|$/g, langSelector + '$&' ) +
+ ' {\n\t' + declarations + '\n}'
+ );
+
+ originalSvg[ 'lang-' + lang ] = grunt.file.read(
+ path.join( sourcePath, moreLangs[ lang ] )
+ );
+ files[ path.join( destinationPath, moreLangs[ lang ] ) ] = originalSvg[ 'lang-' + lang ];
+ }
+ }
+
+ // Variants
+ this.variantNames.forEach( function ( variantName ) {
+ var variantSvg, destinationFilePath,
+ variant = variants.getVariant( variantName );
+
+ if ( variant === undefined ) {
+ unknownVariants.push( variantName );
+ return;
+ }
+
+ selector = cssSelectors.selectorWithVariant
+ .replace( /{prefix}/g, cssClassPrefix )
+ .replace( /{name}/g, name )
+ .replace( /{variant}/g, variantName );
+
+ for ( direction in file ) {
+ declarations = getDeclarations( variantizeFileName( file[ direction ], variantName ) );
+ rules[ direction ].push( selector + ' {\n\t' + declarations + '\n}' );
+
+ // TODO: Do this in a safer and more clever way
+ variantSvg = originalSvg[ direction ].replace(
+ /<svg[^>]*>/, '$&<style>* { fill: ' + variant.getColor() + ' }</style>'
+ );
+
+ if ( originalSvg[ direction ] === variantSvg ) {
+ uncolorizableImages.push( file[ direction ] );
+ continue;
+ }
+
+ destinationFilePath = path.join(
+ destinationPath,
+ variantizeFileName( file[ direction ], variantName )
+ );
+ files[ destinationFilePath ] = variantSvg;
+
+ for ( lang in moreLangs ) {
+ langSelector = ':lang(' + lang + ')';
+ declarations = getDeclarations( variantizeFileName( moreLangs[ lang ], variantName ) );
+ rules[ direction ].push(
+ '/* @noflip */\n' +
+ selector.replace( /,|$/g, langSelector + '$&' ) +
+ ' {\n\t' + declarations + '\n}'
+ );
+
+ variantSvg = originalSvg[ 'lang-' + lang ].replace(
+ /<svg[^>]*>/, '$&<style>* { fill: ' + variant.getColor() + ' }</style>'
+ );
+
+ if ( originalSvg[ 'lang-' + lang ] === variantSvg ) {
+ uncolorizableImages.push( moreLangs[ lang ] );
+ continue;
+ }
+
+ destinationFilePath = path.join(
+ destinationPath,
+ variantizeFileName( moreLangs[ lang ], variantName )
+ );
+ files[ destinationFilePath ] = variantSvg;
+ }
+ }
+ } );
+
+ if ( unknownVariants.length || uncolorizableImages.length ) {
+ if ( unknownVariants.length ) {
+ grunt.log.error(
+ unknownVariants.length +
+ ' unknown variants requested: ' +
+ unknownVariants.join( ', ' )
+ );
+ }
+ if ( uncolorizableImages.length ) {
+ grunt.log.error(
+ uncolorizableImages.length +
+ ' invalid source images: ' +
+ uncolorizableImages.join( ', ' )
+ );
+ }
+ deferred.reject( 'Failed to generate some images' );
+ } else {
+ deferred.resolve( {
+ rules: rules,
+ files: files
+ } );
+ }
+
+ return deferred.promise;
+ };
+
+ /**
+ * List of source images.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {string} path Images path
+ * @param {VariantList} variants Variants list
+ * @param {Object} options Additional options
+ * @param {Object} data List of image configurations keyed by name
+ */
+ function ImageList( path, variants, options, data ) {
+ var key;
+
+ this.list = {};
+ this.path = path;
+ this.variants = variants;
+ this.options = options;
+
+ for ( key in data ) {
+ this.list[ key ] = new Image( this, key, data[ key ] );
+ }
+ }
+
+ /**
+ * Get image path.
+ *
+ * @return {string} Image path
+ */
+ ImageList.prototype.getPath = function () {
+ return this.path;
+ };
+
+ /**
+ * Get image variants.
+ *
+ * @return {VariantsList} Image variants
+ */
+ ImageList.prototype.getVariants = function () {
+ return this.variants;
+ };
+
+ /**
+ * Get CSS class prefix.
+ *
+ * @return {string} CSS class prefix
+ */
+ ImageList.prototype.getCssClassPrefix = function () {
+ return this.options.prefix || '';
+ };
+
+ /**
+ * Get CSS selectors.
+ *
+ * @return {Object.<string, string>} CSS selectors
+ */
+ ImageList.prototype.getCssSelectors = function () {
+ return {
+ selectorWithoutVariant: this.options.selectorWithoutVariant || '.{prefix}-{name}',
+ selectorWithVariant: this.options.selectorWithVariant || '.{prefix}-{name}-{variant}'
+ };
+ };
+
+ /**
+ * Get CSS file intro.
+ *
+ * @return {string} CSS file intro
+ */
+ ImageList.prototype.getCssIntro = function () {
+ return this.options.intro || '';
+ };
+
+ /**
+ * Get number of images in list.
+ *
+ * @return {number} List length
+ */
+ ImageList.prototype.getLength = function () {
+ return Object.keys( this.list ).length;
+ };
+
+ /**
+ * Generate images and CSS.
+ *
+ * @param {Destination} destination Destination
+ * @return {Q.Promise} Promise resolved with number of generated SVG files
+ */
+ ImageList.prototype.generate = function ( destination ) {
+ var list = this.list,
+ intro = this.getCssIntro();
+ return Q.all( Object.keys( this.list ).map( function ( key ) {
+ return list[ key ].generate( destination );
+ } ) ).then( function ( data ) {
+ var textDirection, stylesheetPath, destinationFilePath, dataFormat;
+ dataFormat = {
+ files: {},
+ rules: {
+ ltr: [],
+ rtl: []
+ }
+ };
+
+ data = data.reduce( function ( a, b ) {
+ for ( destinationFilePath in b.files ) {
+ // This de-duplicates the entries, as the same file can be used by many Images
+ a.files[ destinationFilePath ] = b.files[ destinationFilePath ];
+ }
+ a.rules.ltr = a.rules.ltr.concat( b.rules.ltr );
+ a.rules.rtl = a.rules.rtl.concat( b.rules.rtl );
+ return a;
+ }, dataFormat );
+
+ for ( textDirection in data.rules ) {
+ stylesheetPath = destination.getStylesheetPath( textDirection );
+ grunt.file.write(
+ stylesheetPath,
+ intro + '\n' + data.rules[ textDirection ].join( '\n' )
+ );
+ grunt.log.writeln( 'Created "' + stylesheetPath + '".' );
+ }
+ for ( destinationFilePath in data.files ) {
+ grunt.file.write( destinationFilePath, data.files[ destinationFilePath ] );
+ }
+
+ return Object.keys( data.files ).length;
+ } );
+ };
+
+ /**
+ * Image variant.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {VariantList} list Variant list
+ * @param {string} name Variant name
+ * @param {Object} data Variant options
+ */
+ function Variant( list, name, data ) {
+ // Properties
+ this.list = list;
+ this.name = name;
+ this.color = data.color;
+ this.global = data.global;
+ }
+
+ /**
+ * Check if variant is global.
+ *
+ * @return {boolean} Variant is global
+ */
+ Variant.prototype.isGlobal = function () {
+ return this.global;
+ };
+
+ /**
+ * Get variant color.
+ *
+ * @return {string} CSS color expression
+ */
+ Variant.prototype.getColor = function () {
+ return this.color;
+ };
+
+ /**
+ * List of variants.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {Object} list List of variant configurations keyed by name
+ */
+ function VariantList( data ) {
+ var key;
+
+ this.list = {};
+ this.globals = [];
+
+ for ( key in data ) {
+ this.list[ key ] = new Variant( this, key, data[ key ] );
+ if ( this.list[ key ].isGlobal() ) {
+ this.globals.push( key );
+ }
+ }
+ }
+
+ /**
+ * Get names of global variants.
+ *
+ * @return {string[]} Global variant names
+ */
+ VariantList.prototype.getGlobalVariantNames = function () {
+ return this.globals;
+ };
+
+ /**
+ * Get variant by name.
+ *
+ * @param {string} name Variant name
+ * @return {Variant|undefined} Variant with matching name, or undefined of none exists.
+ */
+ VariantList.prototype.getVariant = function ( name ) {
+ return this.list[ name ];
+ };
+
+ /**
+ * Get number of variants in list.
+ *
+ * @return {number} List length
+ */
+ VariantList.prototype.getLength = function () {
+ return Object.keys( this.list ).length;
+ };
+
+};
diff --git a/vendor/oojs/oojs-ui/build/tasks/typos.js b/vendor/oojs/oojs-ui/build/tasks/typos.js
new file mode 100644
index 00000000..6c0bb4ee
--- /dev/null
+++ b/vendor/oojs/oojs-ui/build/tasks/typos.js
@@ -0,0 +1,89 @@
+/*!
+ * Check files from 'src' for typos; fail if any are found.
+ */
+
+/*jshint node:true */
+module.exports = function ( grunt ) {
+
+ grunt.registerMultiTask( 'typos', function () {
+ var typosData, typosCSRegExp, typosCIRegExp, file,
+ options = this.options( {
+ typos: 'typos.json'
+ } ),
+ files = this.filesSrc,
+ fileCount = files.length,
+ typosJSON = grunt.file.read( options.typos ),
+ typoCount = 0,
+ ok = true;
+
+ try {
+ typosData = JSON.parse( typosJSON );
+ } catch ( e ) {
+ grunt.log.error( 'Could not parse ' + options.typos + ': ' + e );
+ }
+
+ typosData.caseSensitive = typosData.caseSensitive || [];
+ typosData.caseInsensitive = typosData.caseInsensitive || [];
+
+ typoCount += typosData.caseSensitive.length + typosData.caseInsensitive.length;
+
+ function patternMap( typo ) {
+ return typo[ 0 ];
+ }
+
+ if ( typosData.caseSensitive.length ) {
+ typosCSRegExp = new RegExp(
+ '(' + typosData.caseSensitive.map( patternMap ).join( '|' ) + ')', 'g'
+ );
+ }
+
+ if ( typosData.caseInsensitive.length ) {
+ typosCIRegExp = new RegExp(
+ '(' + typosData.caseInsensitive.map( patternMap ).join( '|' ) + ')', 'gi'
+ );
+ }
+
+ function findTypo( line, lineNumber, filepath, typos, flags ) {
+ // Check each pattern to find the replacement
+ typos.forEach( function ( typo ) {
+ var replace,
+ pattern = new RegExp( typo[ 0 ], flags ),
+ matches = line.match( pattern );
+
+ if ( matches ) {
+ ok = false;
+ replace = matches[ 0 ].replace( pattern, typo[ 1 ], flags );
+ grunt.log.error(
+ 'File "' + filepath + '" contains typo "' + matches[ 0 ] + '" on line ' + lineNumber +
+ ', did you mean "' + replace + '"?'
+ );
+ }
+ } );
+ }
+
+ files.forEach( function ( filepath ) {
+ if ( grunt.file.isDir( filepath ) ) {
+ fileCount--;
+ return;
+ }
+ file = grunt.file.read( filepath );
+ file.split( '\n' ).forEach( function ( line, lineNumber ) {
+ // Check for any typos on the line with group expressions
+ if ( typosCSRegExp && typosCSRegExp.test( line ) ) {
+ findTypo( line, lineNumber, filepath, typosData.caseSensitive, 'g' );
+ }
+ if ( typosCIRegExp && typosCIRegExp.test( line ) ) {
+ findTypo( line, lineNumber, filepath, typosData.caseInsensitive, 'gi' );
+ }
+ } );
+ } );
+
+ if ( !ok ) {
+ return false;
+ }
+
+ grunt.log.ok( 'No typos found; ' +
+ fileCount + ' file' + ( fileCount !== 1 ? 's' : '' ) + ' checked for ' +
+ typoCount + ' typo' + ( typoCount !== 1 ? 's' : '' ) + '.' );
+ } );
+};
diff --git a/vendor/oojs/oojs-ui/build/typos.json b/vendor/oojs/oojs-ui/build/typos.json
new file mode 100644
index 00000000..5916d77e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/build/typos.json
@@ -0,0 +1,17 @@
+{
+ "caseSensitive": [
+ [ "@returns", "@return" ]
+ ],
+ "caseInsensitive": [
+ [ "paralell", "parallel" ],
+ [ "properites", "properties" ],
+ [ "visiblit(ies|y)", "visibilit$1" ],
+ [ "movablilties", "movabilities" ],
+ [ "inpsect(ors?|ion)", "inspect$1" ],
+ [ "\bteh\b", "the" ],
+ [ "intialization", "initialization" ],
+ [ "durring", "during" ],
+ [ "contian", "contain" ],
+ [ "occured", "occurred" ]
+ ]
+}
diff --git a/vendor/oojs/oojs-ui/composer.json b/vendor/oojs/oojs-ui/composer.json
new file mode 100644
index 00000000..0907de12
--- /dev/null
+++ b/vendor/oojs/oojs-ui/composer.json
@@ -0,0 +1,24 @@
+{
+ "name": "oojs/oojs-ui",
+ "description": "Provides library of common widgets, layouts, and windows.",
+ "homepage": "https://www.mediawiki.org/wiki/OOjs_UI",
+ "license": "MIT",
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "0.8.*",
+ "mediawiki/mediawiki-codesniffer": "0.1.0",
+ "squizlabs/php_codesniffer": "2.1.*"
+ },
+ "autoload": {
+ "classmap": ["php/"]
+ },
+ "scripts": {
+ "test": [
+ "parallel-lint . --exclude vendor",
+ "phpcs $PHPCS_ARGS --standard=vendor/mediawiki/mediawiki-codesniffer/MediaWiki --ignore=vendor --extensions=php,php5,inc -v ."
+ ]
+ }
+}
+
diff --git a/vendor/oojs/oojs-ui/demos/demo.js b/vendor/oojs/oojs-ui/demos/demo.js
new file mode 100644
index 00000000..a1d39314
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/demo.js
@@ -0,0 +1,504 @@
+/**
+ * @class
+ * @extends {OO.ui.Element}
+ *
+ * @constructor
+ */
+OO.ui.Demo = function OoUiDemo() {
+ // Parent
+ OO.ui.Demo.super.call( this );
+
+ // Normalization
+ this.normalizeHash();
+
+ // Properties
+ this.stylesheetLinks = this.getStylesheetLinks();
+ this.mode = this.getCurrentMode();
+ this.$menu = $( '<div>' );
+ this.pageDropdown = new OO.ui.DropdownWidget( {
+ menu: {
+ items: [
+ new OO.ui.MenuOptionWidget( { data: 'dialogs', label: 'Dialogs' } ),
+ new OO.ui.MenuOptionWidget( { data: 'icons', label: 'Icons' } ),
+ new OO.ui.MenuOptionWidget( { data: 'toolbars', label: 'Toolbars' } ),
+ new OO.ui.MenuOptionWidget( { data: 'widgets', label: 'Widgets' } )
+ ]
+ },
+ classes: [ 'oo-ui-demo-pageDropdown' ]
+ } );
+ this.pageMenu = this.pageDropdown.getMenu();
+ this.themeSelect = new OO.ui.ButtonSelectWidget().addItems( [
+ new OO.ui.ButtonOptionWidget( { data: 'mediawiki', label: 'MediaWiki' } ),
+ new OO.ui.ButtonOptionWidget( { data: 'apex', label: 'Apex' } )
+ ] );
+ this.graphicsSelect = new OO.ui.ButtonSelectWidget().addItems( [
+ new OO.ui.ButtonOptionWidget( { data: 'mixed', label: 'Mixed' } ),
+ new OO.ui.ButtonOptionWidget( { data: 'vector', label: 'Vector' } ),
+ new OO.ui.ButtonOptionWidget( { data: 'raster', label: 'Raster' } )
+ ] );
+ this.directionSelect = new OO.ui.ButtonSelectWidget().addItems( [
+ new OO.ui.ButtonOptionWidget( { data: 'ltr', label: 'LTR' } ),
+ new OO.ui.ButtonOptionWidget( { data: 'rtl', label: 'RTL' } )
+ ] );
+
+ // Events
+ this.pageMenu.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
+ this.themeSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
+ this.graphicsSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
+ this.directionSelect.on( 'choose', OO.ui.bind( this.onModeChange, this ) );
+
+ // Initialization
+ this.pageMenu.selectItemByData( this.mode.page );
+ this.themeSelect.selectItemByData( this.mode.theme );
+ this.graphicsSelect.selectItemByData( this.mode.graphics );
+ this.directionSelect.selectItemByData( this.mode.direction );
+ this.$menu
+ .addClass( 'oo-ui-demo-menu' )
+ .append(
+ this.pageDropdown.$element,
+ this.themeSelect.$element,
+ this.graphicsSelect.$element,
+ this.directionSelect.$element
+ );
+ this.$element
+ .addClass( 'oo-ui-demo' )
+ .append( this.$menu );
+ $( 'body' ).addClass( 'oo-ui-' + this.mode.direction );
+ // Correctly apply direction to the <html> tags as well
+ $( 'html' ).attr( 'dir', this.mode.direction );
+ $( 'head' ).append( this.stylesheetLinks );
+ OO.ui.theme = new ( this.constructor.static.themes[ this.mode.theme ].theme )();
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.Demo, OO.ui.Element );
+
+/* Static Properties */
+
+/**
+ * Available pages.
+ *
+ * Populated by each of the page scripts in the `pages` directory.
+ *
+ * @static
+ * @property {Object.<string,Function>} pages List of functions that render a page, keyed by
+ * symbolic page name
+ */
+OO.ui.Demo.static.pages = {};
+
+/**
+ * Available themes.
+ *
+ * List of theme descriptions, each containing a `fileSuffix` property used for linking to the
+ * correct stylesheet file and a `theme` property containing a theme class
+ *
+ * @static
+ * @property {Object.<string,Object>}
+ */
+OO.ui.Demo.static.themes = {
+ mediawiki: {
+ fileSuffix: '-mediawiki',
+ additionalSuffixes: [
+ '-icons-movement',
+ '-icons-content',
+ '-icons-alerts',
+ '-icons-interactions',
+ '-icons-moderation',
+ '-icons-editing-core',
+ '-icons-editing-styling',
+ '-icons-editing-list',
+ '-icons-editing-advanced',
+ '-icons-media',
+ '-icons-location',
+ '-icons-user',
+ '-icons-layout',
+ '-icons-wikimedia'
+ ],
+ theme: OO.ui.MediaWikiTheme
+ },
+ apex: {
+ fileSuffix: '-apex',
+ additionalSuffixes: [
+ '-icons-movement',
+ '-icons-moderation',
+ '-icons-editing-core',
+ '-icons-editing-styling',
+ '-icons-editing-list',
+ '-icons-editing-advanced'
+ ],
+ theme: OO.ui.ApexTheme
+ }
+};
+
+/**
+ * Available graphics formats.
+ *
+ * List of graphics format descriptions, each containing a `fileSuffix` property used for linking
+ * to the correct stylesheet file.
+ *
+ * @static
+ * @property {Object.<string,Object>}
+ */
+OO.ui.Demo.static.graphics = {
+ mixed: { fileSuffix: '' },
+ vector: { fileSuffix: '.vector' },
+ raster: { fileSuffix: '.raster' }
+};
+
+/**
+ * Available text directions.
+ *
+ * List of text direction descriptions, each containing a `fileSuffix` property used for linking to
+ * the correct stylesheet file.
+ *
+ * @static
+ * @property {Object.<string,Object>}
+ */
+OO.ui.Demo.static.directions = {
+ ltr: { fileSuffix: '' },
+ rtl: { fileSuffix: '.rtl' }
+};
+
+/**
+ * Default page.
+ *
+ * Set by one of the page scripts in the `pages` directory.
+ *
+ * @static
+ * @property {string|null}
+ */
+OO.ui.Demo.static.defaultPage = null;
+
+/**
+ * Default page.
+ *
+ * Set by one of the page scripts in the `pages` directory.
+ *
+ * @static
+ * @property {string}
+ */
+OO.ui.Demo.static.defaultTheme = 'mediawiki';
+
+/**
+ * Default page.
+ *
+ * Set by one of the page scripts in the `pages` directory.
+ *
+ * @static
+ * @property {string}
+ */
+OO.ui.Demo.static.defaultGraphics = 'vector';
+
+/**
+ * Default page.
+ *
+ * Set by one of the page scripts in the `pages` directory.
+ *
+ * @static
+ * @property {string}
+ */
+OO.ui.Demo.static.defaultDirection = 'ltr';
+
+/* Methods */
+
+/**
+ * Load the demo page. Must be called after $element is attached.
+ */
+OO.ui.Demo.prototype.initialize = function () {
+ var demo = this,
+ promises = $( this.stylesheetLinks ).map( function () {
+ return $( this ).data( 'load-promise' );
+ } );
+ $.when.apply( $, promises )
+ .done( function () {
+ demo.constructor.static.pages[ demo.mode.page ]( demo );
+ } )
+ .fail( function () {
+ demo.$element.append( $( '<p>' ).text( 'Demo styles failed to load.' ) );
+ } );
+};
+
+/**
+ * Handle mode change events.
+ *
+ * Will load a new page.
+ */
+OO.ui.Demo.prototype.onModeChange = function () {
+ var page = this.pageMenu.getSelectedItem().getData(),
+ theme = this.themeSelect.getSelectedItem().getData(),
+ direction = this.directionSelect.getSelectedItem().getData(),
+ graphics = this.graphicsSelect.getSelectedItem().getData();
+
+ location.hash = '#' + [ page, theme, graphics, direction ].join( '-' );
+};
+
+/**
+ * Get a list of mode factors.
+ *
+ * Factors are a mapping between symbolic names used in the URL hash and internal information used
+ * to act on those symbolic names.
+ *
+ * Factor lists are in URL order: page, theme, graphics, direction. Page contains the symbolic
+ * page name, others contain file suffixes.
+ *
+ * @return {Object[]} List of mode factors, keyed by symbolic name
+ */
+OO.ui.Demo.prototype.getFactors = function () {
+ var key,
+ factors = [ {}, {}, {}, {} ];
+
+ for ( key in this.constructor.static.pages ) {
+ factors[ 0 ][ key ] = key;
+ }
+ for ( key in this.constructor.static.themes ) {
+ factors[ 1 ][ key ] = this.constructor.static.themes[ key ].fileSuffix;
+ }
+ for ( key in this.constructor.static.graphics ) {
+ factors[ 2 ][ key ] = this.constructor.static.graphics[ key ].fileSuffix;
+ }
+ for ( key in this.constructor.static.directions ) {
+ factors[ 3 ][ key ] = this.constructor.static.directions[ key ].fileSuffix;
+ }
+
+ return factors;
+};
+
+/**
+ * Get a list of default factors.
+ *
+ * Factor defaults are in URL order: page, theme, graphics, direction. Each contains a symbolic
+ * factor name which should be used as a fallback when the URL hash is missing or invalid.
+ *
+ * @return {Object[]} List of default factors
+ */
+OO.ui.Demo.prototype.getDefaultFactorValues = function () {
+ return [
+ this.constructor.static.defaultPage,
+ this.constructor.static.defaultTheme,
+ this.constructor.static.defaultGraphics,
+ this.constructor.static.defaultDirection
+ ];
+};
+
+/**
+ * Parse the current URL hash into factor values.
+ *
+ * @return {string[]} Factor values in URL order: page, theme, graphics, direction
+ */
+OO.ui.Demo.prototype.getCurrentFactorValues = function () {
+ return location.hash.slice( 1 ).split( '-' );
+};
+
+/**
+ * Get the current mode.
+ *
+ * Generated from parsed URL hash values.
+ *
+ * @return {Object} List of factor values keyed by factor name
+ */
+OO.ui.Demo.prototype.getCurrentMode = function () {
+ var factorValues = this.getCurrentFactorValues();
+
+ return {
+ page: factorValues[ 0 ],
+ theme: factorValues[ 1 ],
+ graphics: factorValues[ 2 ],
+ direction: factorValues[ 3 ]
+ };
+};
+
+/**
+ * Get link elements for the current mode.
+ *
+ * @return {HTMLElement[]} List of link elements
+ */
+OO.ui.Demo.prototype.getStylesheetLinks = function () {
+ var i, len, links, fragments,
+ factors = this.getFactors(),
+ theme = this.getCurrentFactorValues()[ 1 ],
+ suffixes = this.constructor.static.themes[ theme ].additionalSuffixes || [],
+ urls = [];
+
+ // Translate modes to filename fragments
+ fragments = this.getCurrentFactorValues().map( function ( val, index ) {
+ return factors[ index ][ val ];
+ } );
+
+ // Theme styles
+ urls.push( '../dist/oojs-ui' + fragments.slice( 1 ).join( '' ) + '.css' );
+ for ( i = 0, len = suffixes.length; i < len; i++ ) {
+ urls.push( '../dist/oojs-ui' + fragments[1] + suffixes[i] + fragments.slice( 2 ).join( '' ) + '.css' );
+ }
+
+ // Demo styles
+ urls.push( 'styles/demo' + fragments[ 3 ] + '.css' );
+
+ // Add link tags
+ links = urls.map( function ( url ) {
+ var
+ link = document.createElement( 'link' ),
+ $link = $( link ),
+ deferred = $.Deferred();
+ $link.data( 'load-promise', deferred.promise() );
+ $link.on( {
+ load: deferred.resolve,
+ error: deferred.reject
+ } );
+ link.rel = 'stylesheet';
+ link.href = url;
+ return link;
+ } );
+
+ return links;
+};
+
+/**
+ * Normalize the URL hash.
+ */
+OO.ui.Demo.prototype.normalizeHash = function () {
+ var i, len, factorValues,
+ modes = [],
+ factors = this.getFactors(),
+ defaults = this.getDefaultFactorValues();
+
+ factorValues = this.getCurrentFactorValues();
+ for ( i = 0, len = factors.length; i < len; i++ ) {
+ modes[ i ] = factors[ i ][ factorValues[ i ] ] !== undefined ? factorValues[ i ] : defaults[ i ];
+ }
+
+ // Update hash
+ location.hash = modes.join( '-' );
+};
+
+/**
+ * Destroy demo.
+ */
+OO.ui.Demo.prototype.destroy = function () {
+ $( 'body' ).removeClass( 'oo-ui-ltr oo-ui-rtl' );
+ $( this.stylesheetLinks ).remove();
+ this.$element.remove();
+};
+
+/**
+ * Build a console for interacting with an element.
+ *
+ * @param {OO.ui.Element} item
+ * @param {string} key Variable name for item
+ * @param {string} [item.label=""]
+ * @return {jQuery} Console interface element
+ */
+OO.ui.Demo.prototype.buildConsole = function ( item, key ) {
+ var $toggle, $log, $label, $input, $submit, $console, $form,
+ console = window.console;
+
+ function exec( str ) {
+ var func, ret;
+ /*jshint evil:true */
+ if ( str.indexOf( 'return' ) !== 0 ) {
+ str = 'return ' + str;
+ }
+ try {
+ func = new Function( key, 'item', str );
+ ret = { value: func( item, item ) };
+ } catch ( error ) {
+ ret = {
+ value: undefined,
+ error: error
+ };
+ }
+ return ret;
+ }
+
+ function submit() {
+ var val, result, logval;
+
+ val = $input.val();
+ $input.val( '' );
+ $input[ 0 ].focus();
+ result = exec( val );
+
+ logval = String( result.value );
+ if ( logval === '' ) {
+ logval = '""';
+ }
+
+ $log.append(
+ $( '<div>' )
+ .addClass( 'oo-ui-demo-console-log-line oo-ui-demo-console-log-line-input' )
+ .text( val ),
+ $( '<div>' )
+ .addClass( 'oo-ui-demo-console-log-line oo-ui-demo-console-log-line-return' )
+ .text( logval || result.value )
+ );
+
+ if ( result.error ) {
+ $log.append( $( '<div>' ).addClass( 'oo-ui-demo-console-log-line oo-ui-demo-console-log-line-error' ).text( result.error ) );
+ }
+
+ if ( console && console.log ) {
+ console.log( '[demo]', result.value );
+ if ( result.error ) {
+ if ( console.error ) {
+ console.error( '[demo]', String( result.error ), result.error );
+ } else {
+ console.log( '[demo] Error: ', result.error );
+ }
+ }
+ }
+
+ // Scrol to end
+ $log.prop( 'scrollTop', $log.prop( 'scrollHeight' ) );
+ }
+
+ $toggle = $( '<span>' )
+ .addClass( 'oo-ui-demo-console-toggle' )
+ .attr( 'title', 'Toggle console' )
+ .on( 'click', function ( e ) {
+ e.preventDefault();
+ $console.toggleClass( 'oo-ui-demo-console-collapsed oo-ui-demo-console-expanded' );
+ if ( $input.is( ':visible' ) ) {
+ $input[ 0 ].focus();
+ if ( console && console.log ) {
+ window[ key ] = item;
+ console.log( '[demo]', 'Global ' + key + ' has been set' );
+ console.log( '[demo]', item );
+ }
+ }
+ } );
+
+ $log = $( '<div>' )
+ .addClass( 'oo-ui-demo-console-log' );
+
+ $label = $( '<label>' )
+ .addClass( 'oo-ui-demo-console-label' );
+
+ $input = $( '<input>' )
+ .addClass( 'oo-ui-demo-console-input' )
+ .prop( 'placeholder', '... (predefined: ' + key + ')' );
+
+ $submit = $( '<div>' )
+ .addClass( 'oo-ui-demo-console-submit' )
+ .text( '↵' )
+ .on( 'click', submit );
+
+ $form = $( '<form>' ).on( 'submit', function ( e ) {
+ e.preventDefault();
+ submit();
+ } );
+
+ $console = $( '<div>' )
+ .addClass( 'oo-ui-demo-console oo-ui-demo-console-collapsed' )
+ .append(
+ $toggle,
+ $log,
+ $form.append(
+ $label.append(
+ $input
+ ),
+ $submit
+ )
+ );
+
+ return $console;
+};
diff --git a/vendor/oojs/oojs-ui/demos/index.html b/vendor/oojs/oojs-ui/demos/index.html
new file mode 100644
index 00000000..35ccc69a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/index.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <meta charset="UTF-8">
+ <title>OOjs UI Demos</title>
+ <!-- Prevent scaling on mobile devices which cause problems with dialog sizing -->
+ <meta name="viewport" content="width=device-width, user-scalable=no">
+</head>
+<body>
+ <script src="../node_modules/jquery/dist/jquery.js"></script>
+ <script src="../node_modules/oojs/dist/oojs.jquery.js"></script>
+ <script src="../dist/oojs-ui.js"></script>
+ <script src="../dist/oojs-ui-apex.js"></script>
+ <script src="../dist/oojs-ui-mediawiki.js"></script>
+ <script src="demo.js"></script>
+ <script src="pages/dialogs.js"></script>
+ <script src="pages/icons.js"></script>
+ <script src="pages/widgets.js"></script>
+ <script src="pages/toolbars.js"></script>
+ <script>
+ $( function () {
+ var demo;
+
+ function setup() {
+ if ( demo ) {
+ demo.destroy();
+ }
+ demo = new OO.ui.Demo();
+ $( 'body' ).append( demo.$element );
+ demo.initialize();
+ }
+
+ setup();
+
+ $( window ).on( 'hashchange', setup );
+ } )
+ </script>
+</body>
+</html>
diff --git a/vendor/oojs/oojs-ui/demos/infusion.js b/vendor/oojs/oojs-ui/demos/infusion.js
new file mode 100644
index 00000000..4fa0f64a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/infusion.js
@@ -0,0 +1,66 @@
+// Demonstrate JavaScript 'infusion' of PHP-generated widgets.
+// Used by widgets.php.
+
+var $menu, themeButtons, themes;
+
+// Helper function to get high resolution profiling data, where available.
+function now() {
+ /* global performance */
+ return ( typeof performance !== 'undefined' ) ? performance.now() :
+ Date.now ? Date.now() : new Date().getTime();
+}
+
+// Add a button to infuse everything!
+// (You wouldn't typically do this: you'd only infuse those objects which
+// you needed to attach client-side behaviors to. But our theme-setting
+// code needs a list of all widgets, and it's a good overall test.)
+function infuseAll() {
+ var start, end, all;
+ start = now();
+ all = $( '*[data-ooui]' ).map( function ( _, e ) {
+ return OO.ui.infuse( e.id );
+ } );
+ end = now();
+ window.console.log( 'Infusion time: ' + ( end - start ) );
+ return all;
+}
+$menu = $( '.oo-ui-demo-menu' );
+$menu.append(
+ new OO.ui.ButtonGroupWidget().addItems( [
+ new OO.ui.ButtonWidget( { label: 'Infuse' } )
+ .on( 'click', infuseAll )
+ ] ).$element );
+
+// Hook up the theme switch buttons.
+// This is more like a typical use case: we are just infusing specific
+// named UI elements.
+themeButtons = [
+ // If you're lazy, you can just use OO.ui.infuse. But if you name the
+ // Element type you're expecting, you can get some type checking.
+ OO.ui.ButtonWidget.static.infuse( 'theme-mediawiki' ),
+ OO.ui.ButtonWidget.static.infuse( 'theme-apex' )
+];
+themes = {
+ mediawiki: new OO.ui.MediaWikiTheme(),
+ apex: new OO.ui.ApexTheme()
+};
+function updateTheme( theme ) {
+ OO.ui.theme = themes[theme];
+ $.each( infuseAll(), function ( _, el ) {
+ // FIXME: this isn't really supported, but it makes a neat demo.
+ OO.ui.theme.updateElementClasses( el );
+ } );
+ // A bit of a hack, but it will do for a demo.
+ $( 'link[rel="stylesheet"][title="theme"]' ).attr(
+ 'href',
+ '../dist/oojs-ui-' + theme + '.vector.css'
+ );
+}
+// This is another more typical usage: we take the existing server-side
+// buttons and replace the href with a JS click handler.
+$.each( themeButtons, function ( _, b ) {
+ // Get rid of the old server-side click handling.
+ b.setHref( null );
+ // Add new client-side click handling.
+ b.on( 'click', function () { updateTheme( b.getData() ); } );
+} );
diff --git a/vendor/oojs/oojs-ui/demos/pages/dialogs.js b/vendor/oojs/oojs-ui/demos/pages/dialogs.js
new file mode 100644
index 00000000..6e9da3f8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/pages/dialogs.js
@@ -0,0 +1,602 @@
+OO.ui.Demo.static.pages.dialogs = function ( demo ) {
+ var i, l, name, openButton, DialogClass, config,
+ $demo = demo.$element,
+ fieldset = new OO.ui.FieldsetLayout( { label: 'Dialogs' } ),
+ windows = {},
+ windowManager = new OO.ui.WindowManager();
+
+ function SimpleDialog( config ) {
+ SimpleDialog.super.call( this, config );
+ }
+ OO.inheritClass( SimpleDialog, OO.ui.Dialog );
+ SimpleDialog.static.title = 'Simple dialog';
+ SimpleDialog.prototype.initialize = function () {
+ var closeButton,
+ dialog = this;
+
+ SimpleDialog.super.prototype.initialize.apply( this, arguments );
+ this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ this.content.$element.append( '<p>Dialog content</p>' );
+
+ closeButton = new OO.ui.ButtonWidget( {
+ label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
+ } );
+ closeButton.on( 'click', function () {
+ dialog.close();
+ } );
+
+ this.content.$element.append( closeButton.$element );
+ this.$body.append( this.content.$element );
+ };
+ SimpleDialog.prototype.getBodyHeight = function () {
+ return this.content.$element.outerHeight( true );
+ };
+
+ function ProcessDialog( config ) {
+ ProcessDialog.super.call( this, config );
+ }
+ OO.inheritClass( ProcessDialog, OO.ui.ProcessDialog );
+ ProcessDialog.static.title = 'Process dialog';
+ ProcessDialog.static.actions = [
+ { action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] },
+ { action: 'cancel', label: 'Cancel', flags: 'safe' }
+ ];
+ ProcessDialog.prototype.initialize = function () {
+ ProcessDialog.super.prototype.initialize.apply( this, arguments );
+ this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ this.content.$element.append( '<p>Dialog content</p>' );
+ this.$body.append( this.content.$element );
+ };
+ ProcessDialog.prototype.getActionProcess = function ( action ) {
+ var dialog = this;
+ if ( action ) {
+ return new OO.ui.Process( function () {
+ dialog.close( { action: action } );
+ } );
+ }
+ return ProcessDialog.super.prototype.getActionProcess.call( this, action );
+ };
+ ProcessDialog.prototype.getBodyHeight = function () {
+ return this.content.$element.outerHeight( true );
+ };
+
+ function SearchWidgetDialog( config ) {
+ SearchWidgetDialog.super.call( this, config );
+ this.broken = false;
+ }
+ OO.inheritClass( SearchWidgetDialog, OO.ui.ProcessDialog );
+ SearchWidgetDialog.static.title = 'Search widget dialog';
+ SearchWidgetDialog.prototype.initialize = function () {
+ SearchWidgetDialog.super.prototype.initialize.apply( this, arguments );
+ var i,
+ items = [],
+ searchWidget = new OO.ui.SearchWidget();
+ for ( i = 1; i <= 20; i++ ) {
+ items.push( new OO.ui.OptionWidget( { data: i, label: 'Item ' + i } ) );
+ }
+ searchWidget.results.addItems( items );
+ searchWidget.onQueryChange = function () {};
+ this.$body.append( searchWidget.$element );
+ };
+ SearchWidgetDialog.prototype.getBodyHeight = function () {
+ return 300;
+ };
+ SearchWidgetDialog.static.actions = [
+ { action: 'cancel', label: 'Cancel', flags: 'safe' }
+ ];
+ SearchWidgetDialog.prototype.getActionProcess = function ( action ) {
+ var dialog = this;
+ return new OO.ui.Process( function () {
+ dialog.close( { action: action } );
+ } );
+ };
+
+ function BrokenDialog( config ) {
+ BrokenDialog.super.call( this, config );
+ this.broken = false;
+ }
+ OO.inheritClass( BrokenDialog, OO.ui.ProcessDialog );
+ BrokenDialog.static.title = 'Broken dialog';
+ BrokenDialog.static.actions = [
+ { action: 'save', label: 'Save', flags: [ 'primary', 'constructive' ] },
+ { action: 'delete', label: 'Delete', flags: 'destructive' },
+ { action: 'cancel', label: 'Cancel', flags: 'safe' }
+ ];
+ BrokenDialog.prototype.getBodyHeight = function () {
+ return 250;
+ };
+ BrokenDialog.prototype.initialize = function () {
+ BrokenDialog.super.prototype.initialize.apply( this, arguments );
+ this.content = new OO.ui.PanelLayout( { padded: true } );
+ this.fieldset = new OO.ui.FieldsetLayout( {
+ label: 'Dialog with error handling', icon: 'alert'
+ } );
+ this.description = new OO.ui.LabelWidget( {
+ label: 'Deleting will fail and will not be recoverable. ' +
+ 'Saving will fail the first time, but succeed the second time.'
+ } );
+ this.fieldset.addItems( [ this.description ] );
+ this.content.$element.append( this.fieldset.$element );
+ this.$body.append( this.content.$element );
+ };
+ BrokenDialog.prototype.getSetupProcess = function ( data ) {
+ return BrokenDialog.super.prototype.getSetupProcess.call( this, data )
+ .next( function () {
+ this.broken = true;
+ }, this );
+ };
+ BrokenDialog.prototype.getActionProcess = function ( action ) {
+ return BrokenDialog.super.prototype.getActionProcess.call( this, action )
+ .next( function () {
+ return 1000;
+ }, this )
+ .next( function () {
+ var closing;
+
+ if ( action === 'save' ) {
+ if ( this.broken ) {
+ this.broken = false;
+ return new OO.ui.Error( 'Server did not respond' );
+ }
+ } else if ( action === 'delete' ) {
+ return new OO.ui.Error( 'Permission denied', { recoverable: false } );
+ }
+
+ closing = this.close( { action: action } );
+ if ( action === 'save' ) {
+ // Return a promise to remaing pending while closing
+ return closing;
+ }
+ return BrokenDialog.super.prototype.getActionProcess.call( this, action );
+ }, this );
+ };
+
+ function SamplePage( name, config ) {
+ config = $.extend( { label: 'Sample page', icon: 'Sample icon' }, config );
+ OO.ui.PageLayout.call( this, name, config );
+ this.label = config.label;
+ this.icon = config.icon;
+ this.$element.text( this.label );
+ }
+ OO.inheritClass( SamplePage, OO.ui.PageLayout );
+ SamplePage.prototype.setupOutlineItem = function ( outlineItem ) {
+ SamplePage.super.prototype.setupOutlineItem.call( this, outlineItem );
+ this.outlineItem
+ .setMovable( true )
+ .setRemovable( true )
+ .setIcon( this.icon )
+ .setLabel( this.label );
+ };
+
+ function BookletDialog( config ) {
+ BookletDialog.super.call( this, config );
+ }
+ OO.inheritClass( BookletDialog, OO.ui.ProcessDialog );
+ BookletDialog.static.title = 'Booklet dialog';
+ BookletDialog.static.actions = [
+ { action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] },
+ { action: 'cancel', label: 'Cancel', flags: 'safe' }
+ ];
+ BookletDialog.prototype.getBodyHeight = function () {
+ return 250;
+ };
+ BookletDialog.prototype.initialize = function () {
+ BookletDialog.super.prototype.initialize.apply( this, arguments );
+
+ var dialog = this;
+
+ function changePage( direction ) {
+ var pageIndex = dialog.pages.indexOf( dialog.bookletLayout.getCurrentPage() );
+ pageIndex = ( dialog.pages.length + pageIndex + direction ) % dialog.pages.length;
+ dialog.bookletLayout.setPage( dialog.pages[ pageIndex ].getName() );
+ }
+
+ this.navigationField = new OO.ui.FieldLayout(
+ new OO.ui.ButtonGroupWidget( {
+ items: [
+ new OO.ui.ButtonWidget( {
+ data: 'previous',
+ icon: 'previous'
+ } ).on( 'click', function () {
+ changePage( -1 );
+ } ),
+ new OO.ui.ButtonWidget( {
+ data: 'next',
+ icon: 'next'
+ } ).on( 'click', function () {
+ changePage( 1 );
+ } )
+ ]
+ } ),
+ {
+ label: 'Change page',
+ align: 'top'
+ }
+ );
+
+ this.bookletLayout = new OO.ui.BookletLayout();
+ this.pages = [
+ new SamplePage( 'page-1', { label: 'Page 1', icon: 'window' } ),
+ new SamplePage( 'page-2', { label: 'Page 2', icon: 'window' } ),
+ new SamplePage( 'page-3', { label: 'Page 3', icon: 'window' } )
+ ];
+ this.bookletLayout.addPages( this.pages );
+ this.bookletLayout.connect( this, { set: 'onBookletLayoutSet' } );
+ this.bookletLayout.setPage( 'page-1' );
+
+ this.$body.append( this.bookletLayout.$element );
+ };
+ BookletDialog.prototype.getActionProcess = function ( action ) {
+ if ( action ) {
+ return new OO.ui.Process( function () {
+ this.close( { action: action } );
+ }, this );
+ }
+ return BookletDialog.super.prototype.getActionProcess.call( this, action );
+ };
+ BookletDialog.prototype.onBookletLayoutSet = function ( page ) {
+ page.$element.append( this.navigationField.$element );
+ };
+
+ function OutlinedBookletDialog( config ) {
+ OutlinedBookletDialog.super.call( this, config );
+ }
+ OO.inheritClass( OutlinedBookletDialog, OO.ui.ProcessDialog );
+ OutlinedBookletDialog.static.title = 'Booklet dialog';
+ OutlinedBookletDialog.static.actions = [
+ { action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] },
+ { action: 'cancel', label: 'Cancel', flags: 'safe' }
+ ];
+ OutlinedBookletDialog.prototype.getBodyHeight = function () {
+ return 250;
+ };
+ OutlinedBookletDialog.prototype.initialize = function () {
+ OutlinedBookletDialog.super.prototype.initialize.apply( this, arguments );
+ this.bookletLayout = new OO.ui.BookletLayout( {
+ outlined: true
+ } );
+ this.pages = [
+ new SamplePage( 'small', { label: 'Small', icon: 'window' } ),
+ new SamplePage( 'medium', { label: 'Medium', icon: 'window' } ),
+ new SamplePage( 'large', { label: 'Large', icon: 'window' } ),
+ new SamplePage( 'larger', { label: 'Larger', icon: 'window' } ),
+ new SamplePage( 'full', { label: 'Full', icon: 'window' } )
+ ];
+
+ this.bookletLayout.addPages( this.pages );
+ this.bookletLayout.connect( this, { set: 'onBookletLayoutSet' } );
+ this.$body.append( this.bookletLayout.$element );
+ };
+ OutlinedBookletDialog.prototype.getActionProcess = function ( action ) {
+ if ( action ) {
+ return new OO.ui.Process( function () {
+ this.close( { action: action } );
+ }, this );
+ }
+ return OutlinedBookletDialog.super.prototype.getActionProcess.call( this, action );
+ };
+ OutlinedBookletDialog.prototype.onBookletLayoutSet = function ( page ) {
+ this.setSize( page.getName() );
+ };
+ OutlinedBookletDialog.prototype.getSetupProcess = function ( data ) {
+ return OutlinedBookletDialog.super.prototype.getSetupProcess.call( this, data )
+ .next( function () {
+ this.bookletLayout.setPage( this.getSize() );
+ }, this );
+ };
+
+ function SampleCard( name, config ) {
+ config = $.extend( { label: 'Sample card' }, config );
+ OO.ui.CardLayout.call( this, name, config );
+ this.label = config.label;
+ this.$element.text( this.label );
+ }
+ OO.inheritClass( SampleCard, OO.ui.CardLayout );
+ SampleCard.prototype.setupTabItem = function ( tabItem ) {
+ SampleCard.super.prototype.setupTabItem.call( this, tabItem );
+ this.tabItem.setLabel( this.label );
+ };
+
+ function IndexedDialog( config ) {
+ IndexedDialog.super.call( this, config );
+ }
+ OO.inheritClass( IndexedDialog, OO.ui.ProcessDialog );
+ IndexedDialog.static.title = 'Index dialog';
+ IndexedDialog.static.actions = [
+ { action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] },
+ { action: 'cancel', label: 'Cancel', flags: 'safe' }
+ ];
+ IndexedDialog.prototype.getBodyHeight = function () {
+ return 250;
+ };
+ IndexedDialog.prototype.initialize = function () {
+ IndexedDialog.super.prototype.initialize.apply( this, arguments );
+ this.indexLayout = new OO.ui.IndexLayout();
+ this.cards = [
+ new SampleCard( 'first', { label: 'One' } ),
+ new SampleCard( 'second', { label: 'Two' } ),
+ new SampleCard( 'third', { label: 'Three' } ),
+ new SampleCard( 'fourth', { label: 'Four' } )
+ ];
+
+ this.indexLayout.addCards( this.cards );
+ this.$body.append( this.indexLayout.$element );
+
+ this.indexLayout.getTabs().getItemFromData( 'fourth' ).setDisabled( true );
+ };
+ IndexedDialog.prototype.getActionProcess = function ( action ) {
+ if ( action ) {
+ return new OO.ui.Process( function () {
+ this.close( { action: action } );
+ }, this );
+ }
+ return IndexedDialog.super.prototype.getActionProcess.call( this, action );
+ };
+
+ function MenuDialog( config ) {
+ MenuDialog.super.call( this, config );
+ }
+ OO.inheritClass( MenuDialog, OO.ui.ProcessDialog );
+ MenuDialog.static.title = 'Menu dialog';
+ MenuDialog.static.actions = [
+ { action: 'save', label: 'Done', flags: [ 'primary', 'progressive' ] },
+ { action: 'cancel', label: 'Cancel', flags: 'safe' }
+ ];
+ MenuDialog.prototype.getBodyHeight = function () {
+ return 350;
+ };
+ MenuDialog.prototype.initialize = function () {
+ MenuDialog.super.prototype.initialize.apply( this, arguments );
+ var menuLayout = new OO.ui.MenuLayout(),
+ positionField = new OO.ui.FieldLayout(
+ new OO.ui.ButtonSelectWidget( {
+ items: [
+ new OO.ui.ButtonOptionWidget( {
+ data: 'before',
+ label: 'Before'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'after',
+ label: 'After'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'top',
+ label: 'Top'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'bottom',
+ label: 'Bottom'
+ } )
+ ]
+ } ).on( 'select', function ( item ) {
+ menuLayout.setMenuPosition( item.getData() );
+ } ),
+ {
+ label: 'Menu position',
+ align: 'top'
+ }
+ ),
+ showField = new OO.ui.FieldLayout(
+ new OO.ui.ToggleSwitchWidget( { value: true } ).on( 'change', function ( value ) {
+ menuLayout.toggleMenu( value );
+ } ),
+ {
+ label: 'Show menu',
+ align: 'top'
+ }
+ ),
+ menuPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ),
+ contentPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } );
+
+ menuLayout.$menu.append(
+ menuPanel.$element.append( 'Menu panel' )
+ );
+ menuLayout.$content.append(
+ contentPanel.$element.append(
+ positionField.$element,
+ showField.$element
+ )
+ );
+
+ this.$body.append( menuLayout.$element );
+ };
+ MenuDialog.prototype.getActionProcess = function ( action ) {
+ if ( action ) {
+ return new OO.ui.Process( function () {
+ this.close( { action: action } );
+ }, this );
+ }
+ return MenuDialog.super.prototype.getActionProcess.call( this, action );
+ };
+
+ config = [
+ {
+ name: 'Simple dialog (small)',
+ config: {
+ size: 'small'
+ },
+ data: {
+ title: 'Sample dialog with very long title that does not fit'
+ }
+ },
+ {
+ name: 'Simple dialog (medium)',
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Simple dialog (large)',
+ config: {
+ size: 'large'
+ }
+ },
+ {
+ name: 'Simple dialog (larger)',
+ config: {
+ size: 'larger'
+ }
+ },
+ {
+ name: 'Simple dialog (full)',
+ config: {
+ size: 'full'
+ }
+ },
+ {
+ name: 'Process dialog (medium)',
+ dialogClass: ProcessDialog,
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Process dialog (full)',
+ dialogClass: ProcessDialog,
+ config: {
+ size: 'full'
+ }
+ },
+ {
+ name: 'Search widget dialog (medium)',
+ dialogClass: SearchWidgetDialog,
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Broken dialog (error handling)',
+ dialogClass: BrokenDialog,
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Booklet dialog',
+ dialogClass: BookletDialog,
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Outlined booklet dialog',
+ dialogClass: OutlinedBookletDialog,
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Indexed dialog',
+ dialogClass: IndexedDialog,
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Menu dialog',
+ dialogClass: MenuDialog,
+ config: {
+ size: 'medium'
+ }
+ },
+ {
+ name: 'Message dialog (generic)',
+ dialogClass: OO.ui.MessageDialog,
+ data: {
+ title: 'Continue?',
+ message: 'It may be risky'
+ }
+ },
+ {
+ name: 'Message dialog (verbose)',
+ dialogClass: OO.ui.MessageDialog,
+ data: {
+ title: 'Continue?',
+ message: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque quis laoreet elit. Nam eu velit ullamcorper, volutpat elit sed, viverra massa. Aenean congue aliquam lorem, et laoreet risus condimentum vel. Praesent nec imperdiet mauris. Nunc eros magna, iaculis sit amet ante id, dapibus tristique lorem. Praesent in feugiat lorem, sit amet porttitor eros. Donec sapien turpis, pretium eget ligula id, scelerisque tincidunt diam. Pellentesque a venenatis tortor, at luctus nisl. Quisque vel urna a enim mattis rutrum. Morbi eget consequat nisl. Nam tristique molestie diam ac consequat. Nam varius adipiscing mattis. Praesent sodales volutpat nulla lobortis iaculis. Quisque vel odio eget diam posuere imperdiet. Fusce et iaculis odio. Donec in nibh ut dui accumsan vehicula quis et massa.',
+ verbose: true
+ }
+ },
+ {
+ name: 'Message dialog (1 action)',
+ dialogClass: OO.ui.MessageDialog,
+ data: {
+ title: 'Storage limit reached',
+ message: 'You are out of disk space',
+ actions: [
+ {
+ action: 'accept',
+ label: 'Dismiss',
+ flags: 'primary'
+ }
+ ]
+ }
+ },
+ {
+ name: 'Message dialog (2 actions)',
+ dialogClass: OO.ui.MessageDialog,
+ data: {
+ title: 'Cannot save data',
+ message: 'The server is not responding',
+ actions: [
+ {
+ action: 'reject',
+ label: 'Cancel',
+ flags: 'safe'
+ },
+ {
+ action: 'repeat',
+ label: 'Try again',
+ flags: [ 'primary', 'constructive' ]
+ }
+ ]
+ }
+ },
+ {
+ name: 'Message dialog (3 actions)',
+ dialogClass: OO.ui.MessageDialog,
+ data: {
+ title: 'Delete file?',
+ message: 'The file will be irreversably obliterated. Proceed with caution.',
+ actions: [
+ { action: 'reject', label: 'Cancel', flags: 'safe' },
+ { action: 'reject', label: 'Move file to trash' },
+ {
+ action: 'accept',
+ label: 'Obliterate',
+ flags: [ 'primary', 'destructive' ]
+ }
+ ]
+ }
+ }
+ ];
+
+ function openDialog( name, data ) {
+ windowManager.openWindow( name, data );
+ }
+
+ for ( i = 0, l = config.length; i < l; i++ ) {
+ name = 'window_' + i;
+ DialogClass = config[ i ].dialogClass || SimpleDialog;
+ windows[ name ] = new DialogClass( config[ i ].config );
+ openButton = new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'window',
+ label: $( '<span dir="ltr"></span>' ).text( config[ i ].name )
+ } );
+ openButton.on(
+ 'click', OO.ui.bind( openDialog, this, name, config[ i ].data )
+ );
+ fieldset.addItems( [ new OO.ui.FieldLayout( openButton, { align: 'inline' } ) ] );
+ }
+ windowManager.addWindows( windows );
+
+ $demo.append(
+ new OO.ui.PanelLayout( {
+ expanded: false,
+ framed: true
+ } ).$element
+ .addClass( 'oo-ui-demo-container' )
+ .append( fieldset.$element ),
+ windowManager.$element
+ );
+};
diff --git a/vendor/oojs/oojs-ui/demos/pages/icons.js b/vendor/oojs/oojs-ui/demos/pages/icons.js
new file mode 100644
index 00000000..aec2204c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/pages/icons.js
@@ -0,0 +1,295 @@
+OO.ui.Demo.static.pages.icons = function ( demo ) {
+ var i, len, iconSet, iconsFieldset, iconButton, selector,
+ icons = {
+ core: [
+ 'add',
+ 'advanced',
+ 'alert',
+ 'cancel',
+ 'check',
+ 'circle',
+ 'close',
+ 'code',
+ 'collapse',
+ 'comment',
+ 'ellipsis',
+ 'expand',
+ 'help',
+ 'history',
+ 'info',
+ 'menu',
+ 'next',
+ 'picture',
+ 'previous',
+ 'redo',
+ 'remove',
+ 'search',
+ 'settings',
+ 'tag',
+ 'undo',
+ 'window'
+ ],
+ movement: [
+ 'arrowLast',
+ 'arrowNext',
+ 'downTriangle',
+ 'upTriangle',
+ 'caretLast',
+ 'caretNext',
+ 'caretDown',
+ 'caretUp',
+ 'move'
+ ],
+ content: [
+ 'article',
+ 'articleCheck',
+ 'articleSearch',
+ 'citeArticle',
+ 'book',
+ 'journal',
+ 'newspaper',
+ 'folderPlaceholder',
+ 'die',
+ 'download',
+ 'upload'
+ ],
+ alerts: [
+ 'bell',
+ 'bellOn',
+ 'eye',
+ 'eyeClosed',
+ 'message',
+ 'signature',
+ 'speechBubble',
+ 'speechBubbleAdd',
+ 'speechBubbles'
+ ],
+ interactions: [
+ 'beta',
+ 'betaLaunch',
+ 'bookmark',
+ 'browser',
+ 'clear',
+ 'clock',
+ 'funnel',
+ 'heart',
+ 'key',
+ 'keyboard',
+ 'logOut',
+ 'newWindow',
+ 'printer',
+ 'ribbonPrize',
+ 'sun',
+ 'watchlist'
+ ],
+ moderation: [
+ 'block',
+ 'blockUndo',
+ 'flag',
+ 'flagUndo',
+ 'lock',
+ 'star',
+ 'trash',
+ 'trashUndo',
+ 'unStar',
+ 'unLock'
+ ],
+ 'editing-core': [
+ 'edit',
+ 'editLock',
+ 'editUndo',
+ 'link',
+ 'linkExternal',
+ 'linkSecure'
+ ],
+ 'editing-styling': [
+ 'bigger',
+ 'smaller',
+ 'subscript',
+ 'superscript',
+ 'bold',
+ 'italic',
+ 'strikethrough',
+ 'underline',
+ 'textLanguage',
+ 'textDirLTR',
+ 'textDirRTL',
+ 'textStyle'
+ ],
+ 'editing-list': [
+ 'indent',
+ 'listBullet',
+ 'listNumbered',
+ 'outdent'
+ ],
+ 'editing-advanced': [
+ 'alignCentre',
+ 'alignLeft',
+ 'alignRight',
+ 'find',
+ 'insert',
+ 'layout',
+ 'newline',
+ 'noWikiText',
+ 'outline',
+ 'puzzle',
+ 'quotes',
+ 'quotesAdd',
+ 'redirect',
+ 'searchCaseSensitive',
+ 'searchRegularExpression',
+ 'specialCharacter',
+ 'table',
+ 'tableAddColumnAfter',
+ 'tableAddColumnBefore',
+ 'tableAddRowAfter',
+ 'tableAddRowBefore',
+ 'tableCaption',
+ 'tableMergeCells',
+ 'templateAdd',
+ 'translation',
+ 'wikiText'
+ ],
+ media: [
+ 'image',
+ 'imageAdd',
+ 'imageLock',
+ 'photoGallery',
+ 'play',
+ 'stop'
+ ],
+ location: [
+ 'map',
+ 'mapPin',
+ 'mapPinAdd',
+ 'wikitrail'
+ ],
+ user: [
+ 'userActive',
+ 'userAvatar',
+ 'userInactive',
+ 'userTalk'
+ ],
+ layout: [
+ 'stripeFlow',
+ 'stripeSideMenu',
+ 'stripeSummary',
+ 'stripeToC',
+ 'viewCompact',
+ 'viewDetails',
+ 'visionSimulator'
+ ],
+ wikimedia: [
+ 'logoCC',
+ 'logoWikimediaCommons',
+ 'logoWikipedia'
+ ]
+ },
+ indicators = [
+ 'alert',
+ 'down',
+ 'next',
+ 'previous',
+ 'required',
+ 'search',
+ 'up'
+ ],
+ iconsFieldsets = [],
+ iconsButtons = [],
+ indicatorsFieldset = new OO.ui.FieldsetLayout( { label: 'Indicators' } );
+
+ for ( i = 0, len = indicators.length; i < len; i++ ) {
+ indicatorsFieldset.addItems( [
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ indicator: indicators[ i ],
+ framed: false,
+ label: indicators[ i ]
+ } ),
+ { align: 'top' }
+ )
+ ] );
+ }
+ for ( iconSet in icons ) {
+ iconsFieldset = new OO.ui.FieldsetLayout( { label: 'Icons – ' + iconSet } );
+ iconsFieldsets.push( iconsFieldset );
+
+ for ( i = 0, len = icons[ iconSet ].length; i < len; i++ ) {
+ iconButton = new OO.ui.ButtonWidget( {
+ icon: icons[ iconSet ][ i ],
+ framed: false,
+ label: icons[ iconSet ][ i ]
+ } );
+ iconsButtons.push( iconButton );
+ iconsFieldset.addItems( [
+ new OO.ui.FieldLayout(
+ iconButton,
+ { align: 'top' }
+ )
+ ] );
+ }
+ }
+
+ selector = new OO.ui.ButtonSelectWidget( {
+ items: [
+ new OO.ui.ButtonOptionWidget( {
+ label: 'None',
+ flags: [],
+ data: {
+ progressive: false,
+ constructive: false,
+ destructive: false
+ }
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ label: 'Progressive',
+ flags: [ 'progressive' ],
+ data: {
+ progressive: true,
+ constructive: false,
+ destructive: false
+ }
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ label: 'Constructive',
+ flags: [ 'constructive' ],
+ data: {
+ progressive: false,
+ constructive: true,
+ destructive: false
+ }
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ label: 'Destructive',
+ flags: [ 'destructive' ],
+ data: {
+ progressive: false,
+ constructive: false,
+ destructive: true
+ }
+ } )
+ ]
+ } )
+ .on( 'select', function ( selected ) {
+ iconsButtons.forEach( function ( iconButton ) {
+ iconButton.setFlags( selected.getData() );
+ } );
+ } )
+ .selectItemByData( {
+ progressive: false,
+ constructive: false,
+ destructive: false
+ } );
+
+ demo.$element.append(
+ new OO.ui.PanelLayout( {
+ expanded: false,
+ framed: true
+ } ).$element
+ .addClass( 'oo-ui-demo-container oo-ui-demo-icons' )
+ .append(
+ selector.$element,
+ indicatorsFieldset.$element,
+ iconsFieldsets.map( function ( item ) { return item.$element[0]; } )
+ ) );
+};
diff --git a/vendor/oojs/oojs-ui/demos/pages/toolbars.js b/vendor/oojs/oojs-ui/demos/pages/toolbars.js
new file mode 100644
index 00000000..550d8128
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/pages/toolbars.js
@@ -0,0 +1,338 @@
+OO.ui.Demo.static.pages.toolbars = function ( demo ) {
+ var i, toolGroups, saveButton, actionButton, actionButtonDisabled, PopupTool, ToolGroupTool,
+ $demo = demo.$element,
+ $containers = $(),
+ toolFactories = [],
+ toolGroupFactories = [],
+ toolbars = [];
+
+ // Show some random accelerator keys that don't actually work
+ function getToolAccelerator( name ) {
+ return {
+ listTool1: 'Ctrl+Shift+1',
+ listTool2: 'Ctrl+Alt+2',
+ listTool3: 'Cmd+Enter',
+ listTool5: 'Shift+Down',
+ menuTool: 'Ctrl+M'
+ }[ name ];
+ }
+
+ for ( i = 0; i < 4; i++ ) {
+ toolFactories.push( new OO.ui.ToolFactory() );
+ toolGroupFactories.push( new OO.ui.ToolGroupFactory() );
+ toolbars.push( new OO.ui.Toolbar( toolFactories[ i ], toolGroupFactories[ i ], { actions: true } ) );
+ toolbars[ i ].getToolAccelerator = getToolAccelerator;
+ }
+
+ function createTool( toolbar, group, name, icon, title, init, onSelect, displayBothIconAndLabel ) {
+ var Tool = function () {
+ Tool.super.apply( this, arguments );
+ this.toggled = false;
+ if ( init ) {
+ init.call( this );
+ }
+ };
+
+ OO.inheritClass( Tool, OO.ui.Tool );
+
+ Tool.prototype.onSelect = function () {
+ if ( onSelect ) {
+ onSelect.call( this );
+ } else {
+ this.toggled = !this.toggled;
+ this.setActive( this.toggled );
+ }
+ toolbars[ toolbar ].emit( 'updateState' );
+ };
+ Tool.prototype.onUpdateState = function () {};
+
+ Tool.static.name = name;
+ Tool.static.group = group;
+ Tool.static.icon = icon;
+ Tool.static.title = title;
+ Tool.static.displayBothIconAndLabel = !!displayBothIconAndLabel;
+ return Tool;
+ }
+
+ function createToolGroup( toolbar, group ) {
+ $.each( toolGroups[ group ], function ( i, tool ) {
+ var args = tool.slice();
+ args.splice( 0, 0, toolbar, group );
+ toolFactories[ toolbar ].register( createTool.apply( null, args ) );
+ } );
+ }
+
+ function createDisabledToolGroup( parent, name ) {
+ var DisabledToolGroup = function () {
+ DisabledToolGroup.super.apply( this, arguments );
+ this.setDisabled( true );
+ };
+
+ OO.inheritClass( DisabledToolGroup, parent );
+
+ DisabledToolGroup.static.name = name;
+
+ DisabledToolGroup.prototype.onUpdateState = function () {
+ this.setLabel( 'Disabled' );
+ };
+
+ return DisabledToolGroup;
+ }
+
+ toolGroupFactories[ 0 ].register( createDisabledToolGroup( OO.ui.BarToolGroup, 'disabledBar' ) );
+ toolGroupFactories[ 0 ].register( createDisabledToolGroup( OO.ui.ListToolGroup, 'disabledList' ) );
+ toolGroupFactories[ 1 ].register( createDisabledToolGroup( OO.ui.MenuToolGroup, 'disabledMenu' ) );
+
+ PopupTool = function ( toolGroup, config ) {
+ // Parent constructor
+ OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
+ padded: true,
+ label: 'Popup head',
+ head: true
+ } }, config ) );
+
+ this.popup.$body.append( '<p>Popup contents</p>' );
+ };
+
+ OO.inheritClass( PopupTool, OO.ui.PopupTool );
+
+ PopupTool.static.name = 'popupTool';
+ PopupTool.static.group = 'popupTools';
+ PopupTool.static.icon = 'help';
+
+ toolFactories[ 2 ].register( PopupTool );
+
+ ToolGroupTool = function ( toolGroup, config ) {
+ // Parent constructor
+ OO.ui.ToolGroupTool.call( this, toolGroup, config );
+ };
+
+ OO.inheritClass( ToolGroupTool, OO.ui.ToolGroupTool );
+
+ ToolGroupTool.static.name = 'toolGroupTool';
+ ToolGroupTool.static.group = 'barTools';
+ ToolGroupTool.static.groupConfig = {
+ indicator: 'down',
+ include: [ { group: 'moreListTools' } ]
+ };
+
+ toolFactories[ 0 ].register( ToolGroupTool );
+ toolFactories[ 3 ].register( ToolGroupTool );
+
+ // Toolbar
+ toolbars[ 0 ].setup( [
+ {
+ type: 'bar',
+ include: [ { group: 'barTools' } ],
+ demote: [ 'toolGroupTool' ]
+ },
+ {
+ type: 'disabledBar',
+ include: [ { group: 'disabledBarTools' } ]
+ },
+ {
+ type: 'list',
+ indicator: 'down',
+ label: 'List',
+ icon: 'picture',
+ include: [ { group: 'listTools' } ],
+ allowCollapse: [ 'listTool1', 'listTool6' ]
+ },
+ {
+ type: 'disabledList',
+ indicator: 'down',
+ label: 'List',
+ icon: 'picture',
+ include: [ { group: 'disabledListTools' } ]
+ },
+ {
+ type: 'list',
+ indicator: 'down',
+ label: 'Auto-disabling list',
+ icon: 'picture',
+ include: [ { group: 'autoDisableListTools' } ]
+ },
+ {
+ label: 'Catch-all',
+ include: '*'
+ }
+ ] );
+ // Toolbar with action buttons
+ toolbars[ 1 ].setup( [
+ {
+ type: 'menu',
+ indicator: 'down',
+ icon: 'picture',
+ include: [ { group: 'menuTools' } ]
+ },
+ {
+ type: 'disabledMenu',
+ indicator: 'down',
+ icon: 'picture',
+ include: [ { group: 'disabledMenuTools' } ]
+ }
+ ] );
+ // Fake toolbar to be injected into the first toolbar
+ // demonstrating right-aligned menus
+ toolbars[ 2 ].setup( [
+ {
+ include: [ { group: 'popupTools' } ]
+ },
+ {
+ type: 'list',
+ icon: 'menu',
+ include: [ { group: 'listTools' } ]
+ }
+ ] );
+ toolbars[ 3 ].setup( [
+ {
+ type: 'bar',
+ include: [ { group: 'history' } ]
+ },
+ {
+ type: 'menu',
+ indicator: 'down',
+ include: [ { group: 'menuTools' } ]
+ },
+ {
+ type: 'list',
+ indicator: 'down',
+ icon: 'comment',
+ include: [ { group: 'listTools' } ],
+ allowCollapse: [ 'listTool1', 'listTool6' ]
+ },
+ {
+ type: 'bar',
+ include: [ { group: 'link' } ]
+ },
+ {
+ type: 'bar',
+ include: [ { group: 'cite' } ]
+ },
+ {
+ type: 'list',
+ indicator: 'down',
+ label: 'Insert',
+ include: [ { group: 'autoDisableListTools' }, { group: 'unusedStuff' } ]
+ }
+ ] );
+
+ saveButton = new OO.ui.ButtonWidget( { label: 'Save', flags: [ 'progressive', 'primary' ] } );
+ actionButton = new OO.ui.ButtonWidget( { label: 'Action' } );
+ actionButtonDisabled = new OO.ui.ButtonWidget( { label: 'Disabled', disabled: true } );
+ toolbars[ 1 ].$actions
+ .append( actionButton.$element, actionButtonDisabled.$element );
+
+ toolbars[ 3 ].$actions
+ .append( toolbars[ 2 ].$element, saveButton.$element );
+
+ for ( i = 0; i < toolbars.length; i++ ) {
+ toolbars[ i ].emit( 'updateState' );
+ }
+
+ toolGroups = {
+ barTools: [
+ [ 'barTool', 'picture', 'Basic tool in bar' ],
+ [ 'disabledBarTool', 'picture', 'Basic tool in bar disabled', function () { this.setDisabled( true ); } ]
+ ],
+
+ disabledBarTools: [
+ [ 'barToolInDisabled', 'picture', 'Basic tool in disabled bar' ]
+ ],
+
+ listTools: [
+ [ 'listTool', 'picture', 'First basic tool in list' ],
+ [ 'listTool1', 'picture', 'Basic tool in list' ],
+ [ 'listTool3', 'picture', 'Basic disabled tool in list', function () { this.setDisabled( true ); } ],
+ [ 'listTool6', 'picture', 'A final tool' ]
+ ],
+
+ moreListTools: [
+ [ 'listTool2', 'code', 'Another basic tool' ],
+ [ 'listTool4', 'picture', 'More basic tools' ],
+ [ 'listTool5', 'ellipsis', 'And even more' ]
+ ],
+
+ popupTools: [
+ [ 'popupTool' ]
+ ],
+
+ disabledListTools: [
+ [ 'listToolInDisabled', 'picture', 'Basic tool in disabled list' ]
+ ],
+
+ autoDisableListTools: [
+ [ 'autoDisableListTool', 'picture', 'Click to disable this tool', null, function () { this.setDisabled( true ); } ]
+ ],
+
+ menuTools: [
+ [ 'menuTool', 'picture', 'Basic tool' ],
+ [ 'disabledMenuTool', 'picture', 'Basic tool disabled', function () { this.setDisabled( true ); } ]
+ ],
+
+ disabledMenuTools: [
+ [ 'menuToolInDisabled', 'picture', 'Basic tool' ]
+ ],
+
+ unusedStuff: [
+ [ 'unusedTool', 'help', 'This tool is not explicitly used anywhere' ],
+ [ 'unusedTool1', 'help', 'And neither is this one' ]
+ ],
+
+ history: [
+ [ 'undoTool', 'undo', 'Undo' ],
+ [ 'redoTool', 'redo', 'Redo' ]
+ ],
+
+ link: [
+ [ 'linkTool', 'link', 'Link' ]
+ ],
+
+ cite: [
+ [ 'citeTool', 'citeArticle', 'Cite', null, null, true ]
+ ]
+ };
+
+ createToolGroup( 0, 'unusedStuff' );
+ createToolGroup( 0, 'barTools' );
+ createToolGroup( 0, 'disabledBarTools' );
+ createToolGroup( 0, 'listTools' );
+ createToolGroup( 0, 'moreListTools' );
+ createToolGroup( 0, 'disabledListTools' );
+ createToolGroup( 0, 'autoDisableListTools' );
+ createToolGroup( 1, 'menuTools' );
+ createToolGroup( 1, 'disabledMenuTools' );
+ createToolGroup( 2, 'listTools' );
+ createToolGroup( 3, 'history' );
+ createToolGroup( 3, 'link' );
+ createToolGroup( 3, 'cite' );
+ createToolGroup( 3, 'menuTools' );
+ createToolGroup( 3, 'listTools' );
+ createToolGroup( 3, 'moreListTools' );
+ createToolGroup( 3, 'autoDisableListTools' );
+ createToolGroup( 3, 'unusedStuff' );
+
+ for ( i = 0; i < toolbars.length; i++ ) {
+ $containers = $containers.add(
+ new OO.ui.PanelLayout( {
+ expanded: false,
+ framed: true
+ } ).$element
+ .addClass( 'oo-ui-demo-container oo-ui-demo-toolbars' )
+ );
+
+ if ( i === 2 ) {
+ continue;
+ }
+ $containers.eq( i ).append( toolbars[ i ].$element );
+ }
+ $containers.append( '' );
+ $demo.append(
+ $containers.eq( 0 ).append( '<div class="oo-ui-demo-toolbars-contents">Toolbar</div>' ),
+ $containers.eq( 1 ).append( '<div class="oo-ui-demo-toolbars-contents">Toolbar with action buttons</div>' ),
+ $containers.eq( 3 ).append( '<div class="oo-ui-demo-toolbars-contents">Word processor toolbar</div>' )
+ );
+ for ( i = 0; i < toolbars.length; i++ ) {
+ toolbars[ i ].initialize();
+ }
+};
diff --git a/vendor/oojs/oojs-ui/demos/pages/widgets.js b/vendor/oojs/oojs-ui/demos/pages/widgets.js
new file mode 100644
index 00000000..52c6ee87
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/pages/widgets.js
@@ -0,0 +1,1428 @@
+OO.ui.Demo.static.pages.widgets = function ( demo ) {
+ var styles, states, buttonStyleShowcaseWidget, horizontalAlignmentWidget, fieldsets,
+ $demo = demo.$element;
+
+ /**
+ * Draggable group widget containing drag/drop items
+ * @param {Object} [config] Configuration options
+ */
+ function DragDropGroupWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ DragDropGroupWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.DraggableGroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+
+ // Respond to reorder event
+ this.connect( this, { reorder: 'onReorder' } );
+ }
+ /* Setup */
+ OO.inheritClass( DragDropGroupWidget, OO.ui.Widget );
+ OO.mixinClass( DragDropGroupWidget, OO.ui.DraggableGroupElement );
+
+ /**
+ * Respond to order event
+ * @param {OO.ui.DraggableElement} item Reordered item
+ * @param {number} newIndex New index
+ */
+ DragDropGroupWidget.prototype.onReorder = function ( item, newIndex ) {
+ this.addItems( [ item ], newIndex );
+ };
+
+ /**
+ * Drag/drop items
+ * @param {Object} [config] Configuration options
+ */
+ function DragDropItemWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ DragDropItemWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.DraggableElement.call( this, config );
+ }
+ /* Setup */
+ OO.inheritClass( DragDropItemWidget, OO.ui.OptionWidget );
+ OO.mixinClass( DragDropItemWidget, OO.ui.DraggableElement );
+
+ /**
+ * Demo for LookupElement.
+ * @extends OO.ui.TextInputWidget
+ * @mixins OO.ui.LookupElement
+ */
+ function NumberLookupTextInputWidget() {
+ // Parent constructor
+ OO.ui.TextInputWidget.call( this, { validate: 'integer' } );
+ // Mixin constructors
+ OO.ui.LookupElement.call( this );
+ }
+ OO.inheritClass( NumberLookupTextInputWidget, OO.ui.TextInputWidget );
+ OO.mixinClass( NumberLookupTextInputWidget, OO.ui.LookupElement );
+
+ /**
+ * @inheritdoc
+ */
+ NumberLookupTextInputWidget.prototype.getLookupRequest = function () {
+ var
+ value = this.getValue(),
+ deferred = $.Deferred(),
+ delay = 500 + Math.floor( Math.random() * 500 );
+
+ this.isValid().done( function ( valid ) {
+ if ( valid ) {
+ // Resolve with results after a faked delay
+ setTimeout( function () {
+ deferred.resolve( [ value * 1, value * 2, value * 3, value * 4, value * 5 ] );
+ }, delay );
+ } else {
+ // No results when the input contains invalid content
+ deferred.resolve( [] );
+ }
+ } );
+
+ return deferred.promise( { abort: function () {} } );
+ };
+
+ /**
+ * @inheritdoc
+ */
+ NumberLookupTextInputWidget.prototype.getLookupCacheDataFromResponse = function ( response ) {
+ return response || [];
+ };
+
+ /**
+ * @inheritdoc
+ */
+ NumberLookupTextInputWidget.prototype.getLookupMenuOptionsFromData = function ( data ) {
+ var
+ items = [],
+ i, number;
+ for ( i = 0; i < data.length; i++ ) {
+ number = String( data[ i ] );
+ items.push( new OO.ui.MenuOptionWidget( {
+ data: number,
+ label: number
+ } ) );
+ }
+
+ return items;
+ };
+
+ styles = [
+ {},
+ {
+ flags: [ 'progressive' ]
+ },
+ {
+ flags: [ 'constructive' ]
+ },
+ {
+ flags: [ 'destructive' ]
+ },
+ {
+ flags: [ 'primary', 'progressive' ]
+ },
+ {
+ flags: [ 'primary', 'constructive' ]
+ },
+ {
+ flags: [ 'primary', 'destructive' ]
+ }
+ ];
+ states = [
+ {
+ label: 'Button'
+ },
+ {
+ label: 'Button',
+ icon: 'tag'
+ },
+ {
+ label: 'Button',
+ icon: 'tag',
+ indicator: 'down'
+ },
+ {
+ icon: 'tag',
+ title: 'Title text'
+ },
+ {
+ indicator: 'down'
+ },
+ {
+ icon: 'tag',
+ indicator: 'down'
+ },
+ {
+ label: 'Button',
+ disabled: true
+ },
+ {
+ icon: 'tag',
+ title: 'Title text',
+ disabled: true
+ },
+ {
+ indicator: 'down',
+ disabled: true
+ }
+ ];
+ buttonStyleShowcaseWidget = new OO.ui.Widget();
+ $.each( styles, function ( i, style ) {
+ $.each( states, function ( j, state ) {
+ buttonStyleShowcaseWidget.$element.append(
+ new OO.ui.ButtonWidget( $.extend( {}, style, state ) ).$element
+ );
+ } );
+ buttonStyleShowcaseWidget.$element.append( $( '<br>' ) );
+ } );
+
+ horizontalAlignmentWidget = new OO.ui.Widget( {
+ classes: [ 'oo-ui-demo-horizontal-alignment' ]
+ } );
+ horizontalAlignmentWidget.$element.append(
+ new OO.ui.ButtonWidget( { label: 'Button' } ).$element,
+ new OO.ui.ButtonGroupWidget( { items: [
+ new OO.ui.ToggleButtonWidget( { label: 'A' } ),
+ new OO.ui.ToggleButtonWidget( { label: 'B' } )
+ ] } ).$element,
+ new OO.ui.ButtonInputWidget( { label: 'ButtonInput' } ).$element,
+ new OO.ui.TextInputWidget( { value: 'TextInput' } ).$element,
+ new OO.ui.DropdownInputWidget( { options: [
+ {
+ label: 'DropdownInput',
+ data: null
+ }
+ ] } ).$element,
+ new OO.ui.CheckboxInputWidget( { selected: true } ).$element,
+ new OO.ui.RadioInputWidget( { selected: true } ).$element,
+ new OO.ui.LabelWidget( { label: 'Label' } ).$element
+ );
+
+ fieldsets = [
+ new OO.ui.FieldsetLayout( {
+ label: 'Simple buttons',
+ items: [
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( { label: 'Normal' } ),
+ {
+ label: 'ButtonWidget (normal)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Progressive',
+ flags: [ 'progressive' ]
+ } ),
+ {
+ label: 'ButtonWidget (progressive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Constructive',
+ flags: [ 'constructive' ]
+ } ),
+ {
+ label: 'ButtonWidget (constructive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Destructive',
+ flags: [ 'destructive' ]
+ } ),
+ {
+ label: 'ButtonWidget (destructive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Primary progressive',
+ flags: [ 'primary', 'progressive' ]
+ } ),
+ {
+ label: 'ButtonWidget (primary, progressive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Primary constructive',
+ flags: [ 'primary', 'constructive' ]
+ } ),
+ {
+ label: 'ButtonWidget (primary, constructive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Primary destructive',
+ flags: [ 'primary', 'destructive' ]
+ } ),
+ {
+ label: 'ButtonWidget (primary, destructive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Disabled',
+ disabled: true
+ } ),
+ {
+ label: 'ButtonWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Constructive',
+ flags: [ 'constructive' ],
+ disabled: true
+ } ),
+ {
+ label: 'ButtonWidget (constructive, disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Constructive',
+ icon: 'tag',
+ flags: [ 'constructive' ],
+ disabled: true
+ } ),
+ {
+ label: 'ButtonWidget (constructive, icon, disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Icon',
+ icon: 'tag'
+ } ),
+ {
+ label: 'ButtonWidget (icon)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Icon',
+ icon: 'tag',
+ flags: [ 'progressive' ]
+ } ),
+ {
+ label: 'ButtonWidget (icon, progressive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Indicator',
+ indicator: 'down'
+ } ),
+ {
+ label: 'ButtonWidget (indicator)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Indicator',
+ indicator: 'down',
+ flags: [ 'constructive' ]
+ } ),
+ {
+ label: 'ButtonWidget (indicator, constructive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'help',
+ title: 'Icon only'
+ } ),
+ {
+ label: 'ButtonWidget (icon only)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ indicator: 'alert',
+ title: 'Indicator only'
+ } ),
+ {
+ label: 'ButtonWidget (indicator only)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ icon: 'help',
+ title: 'Icon only, framed'
+ } ),
+ {
+ label: 'ButtonWidget (icon only, framed)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ indicator: 'alert',
+ title: 'Indicator only, framed'
+ } ),
+ {
+ label: 'ButtonWidget (indicator only, framed)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'tag',
+ label: 'Labeled'
+ } ),
+ {
+ label: 'ButtonWidget (frameless)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ flags: [ 'progressive' ],
+ icon: 'check',
+ label: 'Progressive'
+ } ),
+ {
+ label: 'ButtonWidget (frameless, progressive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ flags: [ 'warning' ],
+ icon: 'alert',
+ label: 'Warning'
+ } ),
+ {
+ label: 'ButtonWidget (frameless, warning)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ flags: [ 'destructive' ],
+ icon: 'remove',
+ label: 'Destructive'
+ } ),
+ {
+ label: 'ButtonWidget (frameless, destructive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ flags: [ 'constructive' ],
+ icon: 'add',
+ label: 'Constructive'
+ } ),
+ {
+ label: 'ButtonWidget (frameless, constructive)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'tag',
+ label: 'Disabled',
+ disabled: true
+ } ),
+ {
+ label: 'ButtonWidget (frameless, disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ flags: [ 'constructive' ],
+ icon: 'tag',
+ label: 'Constructive',
+ disabled: true
+ } ),
+ {
+ label: 'ButtonWidget (frameless, constructive, disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'tag',
+ indicator: 'down',
+ label: 'Labeled'
+ } ),
+ {
+ label: 'ButtonWidget (frameless, indicator)\u200E',
+ align: 'top'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FieldsetLayout( {
+ label: 'Button sets',
+ items: [
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonGroupWidget( {
+ items: [
+ new OO.ui.ButtonWidget( {
+ icon: 'tag',
+ label: 'One'
+ } ),
+ new OO.ui.ButtonWidget( {
+ label: 'Two'
+ } ),
+ new OO.ui.ButtonWidget( {
+ indicator: 'required',
+ label: 'Three'
+ } )
+ ]
+ } ),
+ {
+ label: 'ButtonGroupWidget',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonSelectWidget( {
+ items: [
+ new OO.ui.ButtonOptionWidget( {
+ data: 'b',
+ icon: 'tag',
+ label: 'One'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'c',
+ label: 'Two'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'd',
+ indicator: 'required',
+ label: 'Three'
+ } )
+ ]
+ } ),
+ {
+ label: 'ButtonSelectWidget',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonSelectWidget( {
+ disabled: true,
+ items: [
+ new OO.ui.ButtonOptionWidget( {
+ data: 'b',
+ icon: 'tag',
+ label: 'One'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'c',
+ label: 'Two'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'd',
+ indicator: 'required',
+ label: 'Three'
+ } )
+ ]
+ } ),
+ {
+ label: 'ButtonSelectWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonSelectWidget( {
+ items: [
+ new OO.ui.ButtonOptionWidget( {
+ data: 'b',
+ icon: 'tag',
+ label: 'One',
+ disabled: true
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'c',
+ label: 'Two'
+ } ),
+ new OO.ui.ButtonOptionWidget( {
+ data: 'd',
+ indicator: 'required',
+ label: 'Three'
+ } )
+ ]
+ } ),
+ {
+ label: 'ButtonSelectWidget (disabled items)\u200E',
+ align: 'top'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FieldsetLayout( {
+ label: 'Button style showcase',
+ items: [
+ new OO.ui.FieldLayout(
+ buttonStyleShowcaseWidget,
+ {
+ align: 'top'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FieldsetLayout( {
+ label: 'Form widgets',
+ items: [
+ new OO.ui.FieldLayout(
+ new OO.ui.CheckboxInputWidget( {
+ selected: true
+ } ),
+ {
+ align: 'inline',
+ label: 'CheckboxInputWidget'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.CheckboxInputWidget( {
+ selected: true,
+ disabled: true
+ } ),
+ {
+ align: 'inline',
+ label: 'CheckboxInputWidget (disabled)\u200E'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.RadioInputWidget( {
+ name: 'oojs-ui-radio-demo'
+ } ),
+ {
+ align: 'inline',
+ label: 'Connected RadioInputWidget #1'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.RadioInputWidget( {
+ name: 'oojs-ui-radio-demo',
+ selected: true
+ } ),
+ {
+ align: 'inline',
+ label: 'Connected RadioInputWidget #2'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.RadioInputWidget( {
+ selected: true,
+ disabled: true
+ } ),
+ {
+ align: 'inline',
+ label: 'RadioInputWidget (disabled)\u200E'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.RadioSelectWidget( {
+ items: [
+ new OO.ui.RadioOptionWidget( {
+ data: 'Cat',
+ label: 'Cat'
+ } ),
+ new OO.ui.RadioOptionWidget( {
+ data: 'Dog',
+ label: 'Dog'
+ } ),
+ new OO.ui.RadioOptionWidget( {
+ data: 'Goldfish',
+ label: 'Goldfish',
+ disabled: true
+ } )
+ ]
+ } ),
+ {
+ align: 'top',
+ label: 'RadioSelectWidget'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ToggleSwitchWidget(),
+ {
+ label: 'ToggleSwitchWidget',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ToggleSwitchWidget( { disabled: true } ),
+ {
+ label: 'ToggleSwitchWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ToggleSwitchWidget( { disabled: true, value: true } ),
+ {
+ label: 'ToggleSwitchWidget (disabled, checked)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ToggleButtonWidget( { label: 'Toggle' } ),
+ {
+ label: 'ToggleButtonWidget',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ToggleButtonWidget( { label: 'Toggle', value: true } ),
+ {
+ label: 'ToggleButtonWidget (initially active)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ToggleButtonWidget( { icon: 'next' } ),
+ {
+ label: 'ToggleButtonWidget (icon only)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( { value: 'Text input' } ),
+ {
+ label: 'TextInputWidget\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( { icon: 'search' } ),
+ {
+ label: 'TextInputWidget (icon)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ indicator: 'required',
+ required: true,
+ validate: 'non-empty'
+ } ),
+ {
+ label: 'TextInputWidget (indicator, required)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ validate: function ( value ) {
+ return value.length % 2 === 0;
+ }
+ } ),
+ {
+ label: 'TextInputWidget (only allows even number of characters)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( { placeholder: 'Placeholder' } ),
+ {
+ label: 'TextInputWidget (placeholder)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ value: 'Readonly',
+ readOnly: true
+ } ),
+ {
+ label: 'TextInputWidget (readonly)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ multiline: true,
+ value: 'Multiline\nMultiline'
+ } ),
+ {
+ label: 'TextInputWidget (multiline)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ multiline: true,
+ autosize: true,
+ value: 'Autosize\nAutosize\nAutosize\nAutosize'
+ } ),
+ {
+ label: 'TextInputWidget (autosize)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ icon: 'tag',
+ indicator: 'alert',
+ value: 'Text input with label',
+ label: 'Inline label'
+ } ),
+ {
+ label: 'TextInputWidget (label)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ value: 'Disabled',
+ icon: 'tag',
+ indicator: 'required',
+ label: 'Inline label',
+ disabled: true
+ } ),
+ {
+ label: 'TextInputWidget (icon, indicator, label, disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.DropdownWidget( {
+ label: 'Select one',
+ menu: {
+ items: [
+ new OO.ui.MenuOptionWidget( {
+ data: 'a',
+ label: 'First'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'b',
+ label: 'Second',
+ indicator: 'required'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'c',
+ label: 'Third'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'c',
+ label: 'The fourth option has a long label'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'd',
+ label: 'Fifth'
+ } )
+ ]
+ }
+ } ),
+ {
+ label: 'DropdownWidget',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.DropdownWidget( {
+ label: 'Select one',
+ icon: 'tag',
+ menu: {
+ items: [
+ new OO.ui.MenuOptionWidget( {
+ data: 'a',
+ label: 'First'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'b',
+ label: 'Disabled second option',
+ indicator: 'required',
+ disabled: true
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'c',
+ label: 'Third'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'd',
+ label: 'Disabled fourth option with long label',
+ disabled: true
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'c',
+ label: 'Third'
+ } )
+ ]
+ }
+ } ),
+ {
+ label: 'DropdownWidget (disabled options)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.DropdownWidget( {
+ label: 'Select one',
+ disabled: true,
+ menu: {
+ items: [
+ new OO.ui.MenuOptionWidget( {
+ data: 'a',
+ label: 'First'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'b',
+ label: 'Second'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'c',
+ label: 'Third'
+ } ),
+ new OO.ui.MenuOptionWidget( {
+ data: 'd',
+ label: 'Fourth'
+ } )
+ ]
+ }
+ } ),
+ {
+ label: 'DropdownWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.DropdownInputWidget( {
+ options: [
+ {
+ data: 'a',
+ label: 'First'
+ },
+ {
+ data: 'b',
+ label: 'Second'
+ },
+ {
+ data: 'c',
+ label: 'Third'
+ }
+ ],
+ value: 'b'
+ } ),
+ {
+ label: 'DropdownInputWidget',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ComboBoxWidget( {
+ menu: {
+ items: [
+ new OO.ui.MenuOptionWidget( { data: 'asd', label: 'Label for asd' } ),
+ new OO.ui.MenuOptionWidget( { data: 'fgh', label: 'Label for fgh' } ),
+ new OO.ui.MenuOptionWidget( { data: 'jkl', label: 'Label for jkl' } ),
+ new OO.ui.MenuOptionWidget( { data: 'zxc', label: 'Label for zxc' } ),
+ new OO.ui.MenuOptionWidget( { data: 'vbn', label: 'Label for vbn' } )
+ ]
+ }
+ } ),
+ {
+ label: 'ComboBoxWidget',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ComboBoxWidget( {
+ disabled: true,
+ menu: {
+ items: [
+ new OO.ui.MenuOptionWidget( { data: 'asd', label: 'Label for asd' } ),
+ new OO.ui.MenuOptionWidget( { data: 'fgh', label: 'Label for fgh' } ),
+ new OO.ui.MenuOptionWidget( { data: 'jkl', label: 'Label for jkl' } ),
+ new OO.ui.MenuOptionWidget( { data: 'zxc', label: 'Label for zxc' } ),
+ new OO.ui.MenuOptionWidget( { data: 'vbn', label: 'Label for vbn' } )
+ ]
+ }
+ } ),
+ {
+ label: 'ComboBoxWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ComboBoxWidget(),
+ {
+ label: 'ComboBoxWidget (empty)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonInputWidget( {
+ label: 'Submit the form',
+ type: 'submit'
+ } ),
+ {
+ align: 'top',
+ label: 'ButtonInputWidget'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonInputWidget( {
+ label: 'Submit the form',
+ type: 'submit',
+ useInputTag: true
+ } ),
+ {
+ align: 'top',
+ label: 'ButtonInputWidget (using <input/>)\u200E'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FieldsetLayout( {
+ label: 'Horizontal alignment',
+ items: [
+ new OO.ui.FieldLayout(
+ horizontalAlignmentWidget,
+ {
+ label: 'Multiple widgets shown as a single line, ' +
+ 'as used in compact forms or in parts of a bigger widget.',
+ align: 'top'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FieldsetLayout( {
+ label: 'Draggable',
+ items: [
+ new OO.ui.FieldLayout(
+ new DragDropGroupWidget( {
+ orientation: 'horizontal',
+ items: [
+ new DragDropItemWidget( {
+ data: 'item1',
+ label: 'Item 1'
+ } ),
+ new DragDropItemWidget( {
+ data: 'item2',
+ label: 'Item 2'
+ } ),
+ new DragDropItemWidget( {
+ data: 'item3',
+ label: 'Item 3'
+ } ),
+ new DragDropItemWidget( {
+ data: 'item4',
+ label: 'Item 4'
+ } )
+ ]
+ } ),
+ {
+ label: 'DraggableGroupWidget (horizontal)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new DragDropGroupWidget( {
+ items: [
+ new DragDropItemWidget( {
+ data: 'item1',
+ label: 'Item 1'
+ } ),
+ new DragDropItemWidget( {
+ data: 'item2',
+ label: 'Item 2'
+ } ),
+ new DragDropItemWidget( {
+ data: 'item3',
+ label: 'Item 3'
+ } ),
+ new DragDropItemWidget( {
+ data: 'item4',
+ label: 'Item 4'
+ } )
+ ]
+ } ),
+ {
+ label: 'DraggableGroupWidget (vertical)\u200E',
+ align: 'top'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FieldsetLayout( {
+ label: 'Other widgets',
+ items: [
+ new OO.ui.FieldLayout(
+ new OO.ui.IconWidget( {
+ icon: 'picture',
+ title: 'Picture icon'
+ } ),
+ {
+ label: 'IconWidget (normal)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.IconWidget( {
+ icon: 'remove',
+ flags: 'destructive',
+ title: 'Remove icon'
+ } ),
+ {
+ label: 'IconWidget (flagged)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.IconWidget( {
+ icon: 'picture',
+ title: 'Picture icon',
+ disabled: true
+ } ),
+ {
+ label: 'IconWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.IndicatorWidget( {
+ indicator: 'required',
+ title: 'Required indicator'
+ } ),
+ {
+ label: 'IndicatorWidget (normal)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.IndicatorWidget( {
+ indicator: 'required',
+ title: 'Required indicator',
+ disabled: true
+ } ),
+ {
+ label: 'IndicatorWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.LabelWidget( {
+ label: 'Label'
+ } ),
+ {
+ label: 'LabelWidget (normal)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.LabelWidget( {
+ label: 'Label',
+ disabled: true
+ } ),
+ {
+ label: 'LabelWidget (disabled)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.LabelWidget( {
+ label: new OO.ui.HtmlSnippet( '<b>Fancy</b> <i>text</i> <u>formatting</u>!' )
+ } ),
+ {
+ label: 'LabelWidget (with html)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.PopupButtonWidget( {
+ icon: 'info',
+ framed: false,
+ popup: {
+ head: true,
+ label: 'More information',
+ $content: $( '<p>Extra information here.</p>' ),
+ padded: true,
+ align: 'force-left'
+ }
+ } ),
+ {
+ label: 'PopupButtonWidget (frameless, with popup head, align: force-left)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.PopupButtonWidget( {
+ icon: 'info',
+ framed: false,
+ popup: {
+ head: true,
+ label: 'More information',
+ $content: $( '<p>Extra information here.</p>' ),
+ padded: true,
+ align: 'force-right'
+ }
+ } ),
+ {
+ label: 'PopupButtonWidget (frameless, with popup head align: force-right)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.PopupButtonWidget( {
+ icon: 'info',
+ framed: false,
+ popup: {
+ head: true,
+ label: 'More information',
+ $content: $( '<p>Extra information here.</p>' ),
+ padded: true,
+ align: 'backwards'
+ }
+ } ),
+ {
+ label: 'PopupButtonWidget (frameless, with popup head align: backwards)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.PopupButtonWidget( {
+ icon: 'info',
+ framed: false,
+ popup: {
+ head: true,
+ label: 'More information',
+ $content: $( '<p>Extra information here.</p>' ),
+ padded: true,
+ align: 'forwards'
+ }
+ } ),
+ {
+ label: 'PopupButtonWidget (frameless, with popup head align: forwards)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.PopupButtonWidget( {
+ icon: 'menu',
+ label: 'Options',
+ popup: {
+ $content: $( '<p>Additional options here.</p>' ),
+ padded: true,
+ align: 'left'
+ }
+ } ),
+ {
+ label: 'PopupButtonWidget (framed, no popup head)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new NumberLookupTextInputWidget(),
+ {
+ label: 'LookupElement (try inputting an integer)\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ProgressBarWidget( {
+ progress: 33
+ } ),
+ {
+ label: 'Progress bar',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ProgressBarWidget( {
+ progress: false
+ } ),
+ {
+ label: 'Progress bar (indeterminate)\u200E',
+ align: 'top'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FieldsetLayout( {
+ label: 'Field layouts',
+ help: 'I am an additional, helpful information. Lorem ipsum dolor sit amet, cibo pri ' +
+ 'in, duo ex inimicus perpetua complectitur, mel periculis similique at.\u200E',
+ items: [
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonWidget( {
+ label: 'Button'
+ } ),
+ {
+ label: 'FieldLayout with help',
+ help: 'I am an additional, helpful information. Lorem ipsum dolor sit amet, cibo pri ' +
+ 'in, duo ex inimicus perpetua complectitur, mel periculis similique at.\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.ActionFieldLayout(
+ new OO.ui.TextInputWidget( {} ),
+ new OO.ui.ButtonWidget( {
+ label: 'Button'
+ } ),
+ {
+ label: 'ActionFieldLayout aligned left',
+ align: 'left'
+ }
+ ),
+ new OO.ui.ActionFieldLayout(
+ new OO.ui.TextInputWidget( {} ),
+ new OO.ui.ButtonWidget( {
+ label: 'Button'
+ } ),
+ {
+ label: 'ActionFieldLayout aligned inline',
+ align: 'inline'
+ }
+ ),
+ new OO.ui.ActionFieldLayout(
+ new OO.ui.TextInputWidget( {} ),
+ new OO.ui.ButtonWidget( {
+ label: 'Button'
+ } ),
+ {
+ label: 'ActionFieldLayout aligned right',
+ align: 'right'
+ }
+ ),
+ new OO.ui.ActionFieldLayout(
+ new OO.ui.TextInputWidget( {} ),
+ new OO.ui.ButtonWidget( {
+ label: 'Button'
+ } ),
+ {
+ label: 'ActionFieldLayout aligned top',
+ align: 'top'
+ }
+ ),
+ new OO.ui.ActionFieldLayout(
+ new OO.ui.TextInputWidget( {} ),
+ new OO.ui.ButtonWidget( {
+ label: 'Button'
+ } ),
+ {
+ label: 'ActionFieldLayout aligned top with help',
+ help: 'I am an additional, helpful information. Lorem ipsum dolor sit amet, cibo pri ' +
+ 'in, duo ex inimicus perpetua complectitur, mel periculis similique at.\u200E',
+ align: 'top'
+ }
+ ),
+ new OO.ui.ActionFieldLayout(
+ new OO.ui.TextInputWidget( {} ),
+ new OO.ui.ButtonWidget( {
+ label: 'Button'
+ } ),
+ {
+ label: $( '<i>' ).text( 'ActionFieldLayout aligned top with rich text label' ),
+ align: 'top'
+ }
+ )
+ ]
+ } ),
+ new OO.ui.FormLayout( {
+ method: 'GET',
+ action: 'widgets.php',
+ items: [
+ new OO.ui.FieldsetLayout( {
+ label: 'Form layout',
+ items: [
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ name: 'username'
+ } ),
+ {
+ label: 'User name',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.TextInputWidget( {
+ name: 'password',
+ type: 'password'
+ } ),
+ {
+ label: 'Password',
+ align: 'top'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.CheckboxInputWidget( {
+ name: 'rememberme',
+ selected: true
+ } ),
+ {
+ label: 'Remember me',
+ align: 'inline'
+ }
+ ),
+ new OO.ui.FieldLayout(
+ new OO.ui.ButtonInputWidget( {
+ name: 'login',
+ label: 'Log in',
+ type: 'submit',
+ flags: [ 'primary', 'progressive' ],
+ icon: 'check'
+ } ),
+ {
+ label: null,
+ align: 'top'
+ }
+ )
+ ]
+ } )
+ ]
+ } )
+ ];
+
+ $.each( fieldsets, function ( i, fieldsetLayout ) {
+ $.each( fieldsetLayout.getItems(), function ( j, fieldLayout ) {
+ fieldLayout.$element.append(
+ demo.buildConsole( fieldLayout.fieldWidget, 'widget' )
+ );
+ } );
+ } );
+
+ $demo.append(
+ new OO.ui.PanelLayout( {
+ expanded: false,
+ framed: true
+ } ).$element
+ .addClass( 'oo-ui-demo-container' )
+ .append(
+ $( fieldsets.map( function ( fieldset ) { return fieldset.$element[ 0 ]; } ) )
+ )
+ );
+};
+
+OO.ui.Demo.static.defaultPage = 'widgets';
diff --git a/vendor/oojs/oojs-ui/demos/styles/demo.css b/vendor/oojs/oojs-ui/demos/styles/demo.css
new file mode 100644
index 00000000..1c8d6139
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/styles/demo.css
@@ -0,0 +1,250 @@
+body {
+ font-size: 0.8em;
+ font-family: sans-serif;
+}
+
+/* Layout */
+
+.oo-ui-demo-menu > .oo-ui-widget {
+ display: inline-block;
+ vertical-align: middle;
+ margin-right: 1em;
+}
+
+.oo-ui-demo-menu .oo-ui-demo-pageDropdown {
+ width: 10em;
+}
+
+.oo-ui-demo {
+ max-width: 62.5em;
+ margin: 0 auto;
+}
+
+.oo-ui-demo-container {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+@media (max-width: 750px) {
+ .oo-ui-demo-menu {
+ margin-bottom: 1em;
+ }
+ .oo-ui-demo-container {
+ padding: 1em 2em;
+ }
+}
+
+@media (min-width: 751px) {
+ .oo-ui-demo-menu {
+ margin-bottom: 2em;
+ }
+ .oo-ui-demo-container {
+ padding: 2em 4em;
+ }
+}
+
+/* Toolbars demo */
+
+.oo-ui-demo-toolbars {
+ padding: 0;
+ margin-bottom: 2em;
+}
+
+.oo-ui-demo-toolbars-contents {
+ padding: 2em;
+ min-height: 100px;
+}
+
+/* Icons demo */
+
+.oo-ui-demo-icons .oo-ui-buttonSelectWidget {
+ margin-bottom: 2em;
+}
+
+.oo-ui-demo-icons .oo-ui-fieldLayout {
+ display: inline-block;
+ margin-bottom: 0;
+}
+
+.oo-ui-demo-icons .oo-ui-fieldLayout .oo-ui-labelElement-label {
+ display: none;
+}
+
+.oo-ui-demo-icons .oo-ui-fieldLayout .oo-ui-buttonElement-button {
+ margin: 1em 1em 1em 0;
+ width: 12em;
+ opacity: 0.8;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.oo-ui-demo-icons .oo-ui-fieldLayout .oo-ui-buttonElement-button .oo-ui-labelElement-label {
+ text-transform: capitalize;
+ display: inline !important;
+}
+
+.oo-ui-demo-icons .oo-ui-fieldLayout .oo-ui-buttonElement-button:hover {
+ opacity: 1;
+}
+
+/* Widgets demo */
+
+.oo-ui-demo-horizontal-alignment > .oo-ui-checkboxInputWidget,
+.oo-ui-demo-horizontal-alignment > .oo-ui-radioInputWidget,
+.oo-ui-demo-horizontal-alignment > .oo-ui-buttonInputWidget,
+.oo-ui-demo-horizontal-alignment > .oo-ui-textInputWidget,
+.oo-ui-demo-horizontal-alignment > .oo-ui-dropdownInputWidget {
+ display: inline-block;
+}
+
+.oo-ui-demo-horizontal-alignment > .oo-ui-textInputWidget,
+.oo-ui-demo-horizontal-alignment > .oo-ui-dropdownInputWidget {
+ max-width: 10em;
+}
+
+/* Console */
+
+.oo-ui-demo-console {
+ clear: both;
+ position: relative;
+ border: 1px solid transparent;
+ margin-bottom: -2px;
+ line-height: 1.4;
+}
+
+.oo-ui-demo-console-expanded {
+ width: 100%;
+ border-color: #aaa;
+ margin: 0.5em 0;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.oo-ui-demo-console-toggle {
+ float: right;
+ margin-top: -2em;
+ margin-right: -2em;
+ cursor: pointer;
+}
+
+.oo-ui-demo-console-expanded .oo-ui-demo-console-toggle {
+ margin-top: -2.5em;
+}
+
+.oo-ui-demo-console-toggle::after {
+ position: relative;
+ display: inline-block;
+ width: 1.6em;
+ background: #f9f9f9;
+ border-radius: 50%;
+ line-height: 1.6em;
+ color: #999;
+ text-align: center;
+ content: "→";
+}
+
+.oo-ui-demo-console-toggle:hover::after,
+.oo-ui-demo-console-expanded .oo-ui-demo-console-toggle::after {
+ background: #eee;
+ color: #333;
+}
+
+.oo-ui-demo-console-expanded .oo-ui-demo-console-toggle::after {
+ content: "↑";
+}
+
+.oo-ui-demo-console-collapsed .oo-ui-demo-console-log,
+.oo-ui-demo-console-collapsed .oo-ui-demo-console-label,
+.oo-ui-demo-console-collapsed .oo-ui-demo-console-submit {
+ display: none;
+}
+
+.oo-ui-demo-console-log {
+ border: 1px solid #eee;
+ border-width: 0 0 1px 0;
+ max-height: 5em;
+ overflow: auto;
+}
+
+.oo-ui-demo-console-log-line {
+ position: relative;
+ padding-left: 20px;
+}
+
+.oo-ui-demo-console-log-line-input {
+ font-style: italic;
+ color: #555;
+}
+
+.oo-ui-demo-console-log-line-input::before {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 20px;
+ text-align: center;
+ bottom: 0;
+ content: "→";
+}
+
+.oo-ui-demo-console-log-line-error {
+ color: red;
+}
+
+.oo-ui-demo-console-label {
+ position: relative;
+ display: inline-block;
+ width: 100%;
+ padding-left: 20px;
+ padding-right: 20px;
+ margin-right: -20px;
+ cursor: pointer;
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+.oo-ui-demo-console-label::before {
+ display: block;
+ position: absolute;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ width: 20px;
+ text-align: center;
+ content: '→';
+ color: blue;
+}
+
+.oo-ui-demo-console-input {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ width: 100%;
+}
+
+.oo-ui-demo-console-input:focus {
+ outline: 0;
+}
+
+.oo-ui-demo-console-submit {
+ display: inline-block;
+ width: 20px;
+ line-height: 1.6em;
+ text-align: center;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background: #f9f9f9;
+ color: #888;
+ cursor: pointer;
+}
+
+.oo-ui-demo-console-submit:hover {
+ background: #eee;
+ color: #333;
+}
diff --git a/vendor/oojs/oojs-ui/demos/widgets.php b/vendor/oojs/oojs-ui/demos/widgets.php
new file mode 100644
index 00000000..494a7ac1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/demos/widgets.php
@@ -0,0 +1,830 @@
+<?php
+ $autoload = '../vendor/autoload.php';
+ if ( !file_exists( $autoload ) ) {
+ echo '<h1>Did you forget to run <code>composer install</code>?</h1>';
+ exit();
+ }
+ require_once $autoload;
+
+ $theme = ( isset( $_GET['theme'] ) && $_GET['theme'] === 'apex' ) ? 'apex' : 'mediawiki';
+ $themeClass = 'OOUI\\' . ( $theme === 'apex' ? 'Apex' : 'MediaWiki' ) . 'Theme';
+ OOUI\Theme::setSingleton( new $themeClass() );
+
+ $graphicSuffixMap = array(
+ 'mixed' => '',
+ 'vector' => '.vector',
+ 'raster' => '.raster',
+ );
+ $graphic = ( isset( $_GET['graphic'] ) && isset( $graphicSuffixMap[ $_GET['graphic'] ] ) )
+ ? $_GET['graphic'] : 'vector';
+ $graphicSuffix = $graphicSuffixMap[ $graphic ];
+
+ $direction = ( isset( $_GET['direction'] ) && $_GET['direction'] === 'rtl' ) ? 'rtl' : 'ltr';
+ $directionSuffix = $direction === 'rtl' ? '.rtl' : '';
+ OOUI\Element::setDefaultDir( $direction );
+
+ $query = array(
+ 'theme' => $theme,
+ 'graphic' => $graphic,
+ 'direction' => $direction,
+ );
+ $styleFileName = "oojs-ui-$theme$graphicSuffix$directionSuffix.css";
+?>
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <meta charset="UTF-8">
+ <title>OOjs UI Widget Demo</title>
+ <link rel="stylesheet" href="../dist/<?php echo $styleFileName; ?>" title="theme">
+ <link rel="stylesheet" href="styles/demo<?php echo $directionSuffix; ?>.css">
+</head>
+<body class="oo-ui-<?php echo $direction; ?>">
+ <div class="oo-ui-demo">
+ <div class="oo-ui-demo-menu">
+ <?php
+ echo new OOUI\ButtonGroupWidget( array(
+ 'infusable' => true,
+ 'items' => array(
+ new OOUI\ButtonWidget( array(
+ 'id' => 'theme-mediawiki',
+ 'label' => 'MediaWiki',
+ 'data' => 'mediawiki',
+ 'href' => '?' . http_build_query( array_merge( $query, array( 'theme' => 'mediawiki' ) ) ),
+ ) ),
+ new OOUI\ButtonWidget( array(
+ 'id' => 'theme-apex',
+ 'label' => 'Apex',
+ 'data' => 'apex',
+ 'href' => '?' . http_build_query( array_merge( $query, array( 'theme' => 'apex' ) ) ),
+ ) ),
+ )
+ ) );
+ echo new OOUI\ButtonGroupWidget( array(
+ 'infusable' => true,
+ 'items' => array(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Mixed',
+ 'href' => '?' . http_build_query( array_merge( $query, array( 'graphic' => 'mixed' ) ) ),
+ ) ),
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Vector',
+ 'href' => '?' . http_build_query( array_merge( $query, array( 'graphic' => 'vector' ) ) ),
+ ) ),
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Raster',
+ 'href' => '?' . http_build_query( array_merge( $query, array( 'graphic' => 'raster' ) ) ),
+ ) ),
+ )
+ ) );
+ echo new OOUI\ButtonGroupWidget( array(
+ 'infusable' => true,
+ 'items' => array(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'LTR',
+ 'href' => '?' . http_build_query( array_merge( $query, array( 'direction' => 'ltr' ) ) ),
+ ) ),
+ new OOUI\ButtonWidget( array(
+ 'label' => 'RTL',
+ 'href' => '?' . http_build_query( array_merge( $query, array( 'direction' => 'rtl' ) ) ),
+ ) ),
+ )
+ ) );
+ ?>
+ </div>
+ <?php
+ $demoContainer = new OOUI\PanelLayout( array(
+ 'expanded' => false,
+ 'padded' => true,
+ 'framed' => true,
+ ) );
+ $demoContainer->addClasses( array( 'oo-ui-demo-container' ) );
+
+ $styles = array(
+ array(),
+ array(
+ 'flags' => array( 'progressive' ),
+ ),
+ array(
+ 'flags' => array( 'constructive' ),
+ ),
+ array(
+ 'flags' => array( 'destructive' ),
+ ),
+ array(
+ 'flags' => array( 'primary', 'progressive' ),
+ ),
+ array(
+ 'flags' => array( 'primary', 'constructive' ),
+ ),
+ array(
+ 'flags' => array( 'primary', 'destructive' ),
+ ),
+ );
+ $states = array(
+ array(
+ 'label' => 'Button',
+ ),
+ array(
+ 'label' => 'Button',
+ 'icon' => 'tag',
+ ),
+ array(
+ 'label' => 'Button',
+ 'icon' => 'tag',
+ 'indicator' => 'down',
+ ),
+ array(
+ 'icon' => 'tag',
+ 'title' => "Title text",
+ ),
+ array(
+ 'indicator' => 'down',
+ ),
+ array(
+ 'icon' => 'tag',
+ 'indicator' => 'down',
+ ),
+ array(
+ 'label' => 'Button',
+ 'disabled' => true,
+ ),
+ array(
+ 'icon' => 'tag',
+ 'title' => "Title text",
+ 'disabled' => true,
+ ),
+ array(
+ 'indicator' => 'down',
+ 'disabled' => true,
+ ),
+ );
+ $buttonStyleShowcaseWidget = new OOUI\Widget();
+ foreach ( $styles as $style ) {
+ foreach ( $states as $state ) {
+ $buttonStyleShowcaseWidget->appendContent(
+ new OOUI\ButtonWidget( array_merge( $style, $state, array( 'infusable' => true ) ) )
+ );
+ }
+ $buttonStyleShowcaseWidget->appendContent( new OOUI\HtmlSnippet( '<br />' ) );
+ }
+
+ $horizontalAlignmentWidget = new OOUI\Widget( array(
+ 'classes' => array( 'oo-ui-demo-horizontal-alignment' )
+ ) );
+ # Adding content after the fact does not play well with
+ # infusability. We should be using a proper Layout here.
+ $horizontalAlignmentWidget->appendContent(
+ new OOUI\ButtonWidget( array( 'label' => 'Button' ) ),
+ new OOUI\ButtonGroupWidget( array( 'items' => array(
+ new OOUI\ButtonWidget( array( 'label' => 'A' ) ),
+ new OOUI\ButtonWidget( array( 'label' => 'B' ) )
+ ) ) ),
+ new OOUI\ButtonInputWidget( array( 'label' => 'ButtonInput' ) ),
+ new OOUI\TextInputWidget( array( 'value' => 'TextInput' ) ),
+ new OOUI\DropdownInputWidget( array( 'options' => array(
+ array(
+ 'label' => 'DropdownInput',
+ 'data' => null
+ )
+ ) ) ),
+ new OOUI\CheckboxInputWidget( array( 'selected' => true ) ),
+ new OOUI\RadioInputWidget( array( 'selected' => true ) ),
+ new OOUI\LabelWidget( array( 'label' => 'Label' ) )
+ );
+
+ $demoContainer->appendContent( new OOUI\FieldsetLayout( array(
+ 'infusable' => true,
+ 'label' => 'Simple buttons',
+ 'items' => array(
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array( 'label' => 'Normal' ) ),
+ array(
+ 'label' => "ButtonWidget (normal)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Progressive',
+ 'flags' => array( 'progressive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (progressive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Constructive',
+ 'flags' => array( 'constructive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (constructive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Destructive',
+ 'flags' => array( 'destructive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (destructive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Primary progressive',
+ 'flags' => array( 'primary', 'progressive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (primary, progressive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Primary constructive',
+ 'flags' => array( 'primary', 'constructive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (primary, constructive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Primary destructive',
+ 'flags' => array( 'primary', 'destructive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (primary, destructive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Disabled',
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "ButtonWidget (disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Constructive',
+ 'flags' => array( 'constructive' ),
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "ButtonWidget (constructive, disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Constructive',
+ 'icon' => 'tag',
+ 'flags' => array( 'constructive' ),
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "ButtonWidget (constructive, icon, disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Icon',
+ 'icon' => 'tag'
+ ) ),
+ array(
+ 'label' => "ButtonWidget (icon)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Icon',
+ 'icon' => 'tag',
+ 'flags' => array( 'progressive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (icon, progressive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Indicator',
+ 'indicator' => 'down'
+ ) ),
+ array(
+ 'label' => "ButtonWidget (indicator)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Indicator',
+ 'indicator' => 'down',
+ 'flags' => array( 'constructive' )
+ ) ),
+ array(
+ 'label' => "ButtonWidget (indicator, constructive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'framed' => false,
+ 'icon' => 'help',
+ 'title' => 'Icon only'
+ ) ),
+ array(
+ 'label' => "ButtonWidget (icon only)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'framed' => false,
+ 'icon' => 'tag',
+ 'label' => 'Labeled'
+ ) ),
+ array(
+ 'label' => "ButtonWidget (frameless)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'framed' => false,
+ 'flags' => array( 'progressive' ),
+ 'icon' => 'check',
+ 'label' => 'Progressive'
+ ) ),
+ array(
+ 'label' => "ButtonWidget (frameless, progressive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'framed' => false,
+ 'flags' => array( 'destructive' ),
+ 'icon' => 'remove',
+ 'label' => 'Destructive'
+ ) ),
+ array(
+ 'label' => "ButtonWidget (frameless, destructive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'framed' => false,
+ 'flags' => array( 'constructive' ),
+ 'icon' => 'add',
+ 'label' => 'Constructive'
+ ) ),
+ array(
+ 'label' => "ButtonWidget (frameless, constructive)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'framed' => false,
+ 'icon' => 'tag',
+ 'label' => 'Disabled',
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "ButtonWidget (frameless, disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'framed' => false,
+ 'flags' => array( 'constructive' ),
+ 'icon' => 'tag',
+ 'label' => 'Constructive',
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "ButtonWidget (frameless, constructive, disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ )
+ )
+ ) ) );
+ $demoContainer->appendContent( new OOUI\FieldsetLayout( array(
+ 'infusable' => true,
+ 'label' => 'Button sets',
+ 'items' => array(
+ new OOUI\FieldLayout(
+ new OOUI\ButtonGroupWidget( array(
+ 'items' => array(
+ new OOUI\ButtonWidget( array(
+ 'icon' => 'tag',
+ 'label' => 'One'
+ ) ),
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Two'
+ ) ),
+ new OOUI\ButtonWidget( array(
+ 'indicator' => 'required',
+ 'label' => 'Three'
+ ) )
+ )
+ ) ),
+ array(
+ 'label' => 'ButtonGroupWidget',
+ 'align' => 'top'
+ )
+ )
+ )
+ ) ) );
+ # Note that $buttonStyleShowcaseWidget is not infusable,
+ # because the contents would not be preserved -- we assume
+ # that widgets will manage their own contents by default,
+ # but here we've manually appended content to the widget.
+ # If we embed it in an infusable FieldsetLayout, it will be
+ # (recursively) made infusable. We protect the FieldLayout
+ # by wrapping it with a new <div> Tag, so that it won't get
+ # rebuilt during infusion.
+ $wrappedFieldLayout = new OOUI\Tag( 'div' );
+ $wrappedFieldLayout->appendContent(
+ new OOUI\FieldLayout(
+ $buttonStyleShowcaseWidget,
+ array(
+ 'align' => 'top'
+ )
+ )
+ );
+ $demoContainer->appendContent( new OOUI\FieldsetLayout( array(
+ 'infusable' => true,
+ 'label' => 'Button style showcase',
+ 'items' => array( $wrappedFieldLayout ),
+ ) ) );
+ $demoContainer->appendContent( new OOUI\FieldsetLayout( array(
+ 'infusable' => true,
+ 'label' => 'Form widgets',
+ 'items' => array(
+ new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'selected' => true
+ ) ),
+ array(
+ 'align' => 'inline',
+ 'label' => 'CheckboxInputWidget'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'selected' => true,
+ 'disabled' => true
+ ) ),
+ array(
+ 'align' => 'inline',
+ 'label' => "CheckboxInputWidget (disabled)\xE2\x80\x8E"
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\RadioInputWidget( array(
+ 'name' => 'oojs-ui-radio-demo'
+ ) ),
+ array(
+ 'align' => 'inline',
+ 'label' => 'Connected RadioInputWidget #1'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\RadioInputWidget( array(
+ 'name' => 'oojs-ui-radio-demo',
+ 'selected' => true
+ ) ),
+ array(
+ 'align' => 'inline',
+ 'label' => 'Connected RadioInputWidget #2'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\RadioInputWidget( array(
+ 'selected' => true,
+ 'disabled' => true
+ ) ),
+ array(
+ 'align' => 'inline',
+ 'label' => "RadioInputWidget (disabled)\xE2\x80\x8E"
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array( 'value' => 'Text input' ) ),
+ array(
+ 'label' => "TextInputWidget\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array( 'icon' => 'search' ) ),
+ array(
+ 'label' => "TextInputWidget (icon)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array(
+ 'indicator' => 'required',
+ 'required' => true
+ ) ),
+ array(
+ 'label' => "TextInputWidget (indicator, required)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array( 'placeholder' => 'Placeholder' ) ),
+ array(
+ 'label' => "TextInputWidget (placeholder)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array(
+ 'value' => 'Readonly',
+ 'readOnly' => true
+ ) ),
+ array(
+ 'label' => "TextInputWidget (readonly)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array(
+ 'value' => 'Disabled',
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "TextInputWidget (disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array(
+ 'multiline' => true,
+ 'value' => "Multiline\nMultiline"
+ ) ),
+ array(
+ 'label' => "TextInputWidget (multiline)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\DropdownInputWidget( array(
+ 'options' => array(
+ array(
+ 'data' => 'a',
+ 'label' => 'First'
+ ),
+ array(
+ 'data' => 'b',
+ 'label' => 'Second'
+ ),
+ array(
+ 'data' => 'c',
+ 'label' => 'Third'
+ )
+ ),
+ 'value' => 'b'
+ ) ),
+ array(
+ 'label' => 'DropdownInputWidget',
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonInputWidget( array(
+ 'label' => 'Submit the form',
+ 'type' => 'submit'
+ ) ),
+ array(
+ 'align' => 'top',
+ 'label' => "ButtonInputWidget\xE2\x80\x8E"
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonInputWidget( array(
+ 'label' => 'Submit the form',
+ 'type' => 'submit',
+ 'useInputTag' => true
+ ) ),
+ array(
+ 'align' => 'top',
+ 'label' => "ButtonInputWidget (using <input/>)\xE2\x80\x8E"
+ )
+ )
+ )
+ ) ) );
+ # Again, $horizontalAlignmentWidget is not infusable because
+ # it manually added content after creation. If we embed it
+ # in an infusable FieldsetLayout, it will (recursively) be made
+ # infusable. So protect the widget by wrapping it in a
+ # <div> Tag.
+ $wrappedFieldLayout = new OOUI\Tag( 'div' );
+ $wrappedFieldLayout->appendContent(
+ new OOUI\FieldLayout(
+ $horizontalAlignmentWidget,
+ array(
+ 'label' => 'Multiple widgets shown as a single line, ' .
+ 'as used in compact forms or in parts of a bigger widget.',
+ 'align' => 'top'
+ )
+ )
+ );
+ $demoContainer->appendContent( new OOUI\FieldsetLayout( array(
+ 'infusable' => true,
+ 'label' => 'Horizontal alignment',
+ 'items' => array( $wrappedFieldLayout ),
+ ) ) );
+ $demoContainer->appendContent( new OOUI\FieldsetLayout( array(
+ 'infusable' => true,
+ 'label' => 'Other widgets',
+ 'items' => array(
+ new OOUI\FieldLayout(
+ new OOUI\IconWidget( array(
+ 'icon' => 'picture',
+ 'title' => 'Picture icon'
+ ) ),
+ array(
+ 'label' => "IconWidget (normal)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\IconWidget( array(
+ 'icon' => 'remove',
+ 'flags' => 'destructive',
+ 'title' => 'Remove icon'
+ ) ),
+ array(
+ 'label' => "IconWidget (flagged)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\IconWidget( array(
+ 'icon' => 'picture',
+ 'title' => 'Picture icon',
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "IconWidget (disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\IndicatorWidget( array(
+ 'indicator' => 'required',
+ 'title' => 'Required indicator'
+ ) ),
+ array(
+ 'label' => "IndicatorWidget (normal)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\IndicatorWidget( array(
+ 'indicator' => 'required',
+ 'title' => 'Required indicator',
+ 'disabled' => true
+ ) ),
+ array(
+ 'label' => "IndicatorWidget (disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\LabelWidget( array(
+ 'label' => 'Label'
+ ) ),
+ array(
+ 'label' => "LabelWidget (normal)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\LabelWidget( array(
+ 'label' => 'Label',
+ 'disabled' => true,
+ ) ),
+ array(
+ 'label' => "LabelWidget (disabled)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\LabelWidget( array(
+ 'label' => new OOUI\HtmlSnippet( '<b>Fancy</b> <i>text</i> <u>formatting</u>!' ),
+ ) ),
+ array(
+ 'label' => "LabelWidget (with html)\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ )
+ )
+ ) ) );
+ $demoContainer->appendContent( new OOUI\FieldsetLayout( array(
+ 'infusable' => true,
+ 'label' => 'Field layouts',
+ 'help' => 'I am an additional, helpful information. Lorem ipsum dolor sit amet, cibo pri ' .
+ "in, duo ex inimicus perpetua complectitur, mel periculis similique at.\xE2\x80\x8E",
+ 'items' => array(
+ new OOUI\FieldLayout(
+ new OOUI\ButtonWidget( array(
+ 'label' => 'Button'
+ ) ),
+ array(
+ 'label' => 'FieldLayout with help',
+ 'help' => 'I am an additional, helpful information. Lorem ipsum dolor sit amet, cibo pri ' .
+ "in, duo ex inimicus perpetua complectitur, mel periculis similique at.\xE2\x80\x8E",
+ 'align' => 'top'
+ )
+ )
+ )
+ ) ) );
+
+ $demoContainer->appendContent( new OOUI\FormLayout( array(
+ 'infusable' => true,
+ 'method' => 'GET',
+ 'action' => 'widgets.php',
+ 'items' => array(
+ new OOUI\FieldsetLayout( array(
+ 'label' => 'Form layout',
+ 'items' => array(
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array(
+ 'name' => 'username',
+ ) ),
+ array(
+ 'label' => 'User name',
+ 'align' => 'top',
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\TextInputWidget( array(
+ 'name' => 'password',
+ 'type' => 'password',
+ ) ),
+ array(
+ 'label' => 'Password',
+ 'align' => 'top',
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\CheckboxInputWidget( array(
+ 'name' => 'rememberme',
+ 'selected' => true,
+ ) ),
+ array(
+ 'label' => 'Remember me',
+ 'align' => 'inline',
+ )
+ ),
+ new OOUI\FieldLayout(
+ new OOUI\ButtonInputWidget( array(
+ 'name' => 'login',
+ 'label' => 'Log in',
+ 'type' => 'submit',
+ 'flags' => array( 'primary', 'progressive' ),
+ 'icon' => 'check',
+ ) ),
+ array(
+ 'label' => null,
+ 'align' => 'top',
+ )
+ ),
+ )
+ ) )
+ )
+ ) ) );
+
+ echo $demoContainer;
+
+ ?>
+ </div>
+
+ <!-- Demonstrate JavaScript "infusion" of PHP widgets -->
+ <script src="../node_modules/jquery/dist/jquery.js"></script>
+ <script src="../node_modules/oojs/dist/oojs.jquery.js"></script>
+ <script src="../dist/oojs-ui.js"></script>
+ <script src="../dist/oojs-ui-apex.js"></script>
+ <script src="../dist/oojs-ui-mediawiki.js"></script>
+ <script src="./infusion.js"></script>
+</body>
+</html>
diff --git a/vendor/oojs/oojs-ui/i18n/ace.json b/vendor/oojs/oojs-ui/i18n/ace.json
new file mode 100644
index 00000000..0fdc1a89
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ace.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Si Gam Acèh"
+ ]
+ },
+ "ooui-outline-control-move-down": "Pinah item u yup",
+ "ooui-outline-control-move-up": "Pinah item u ateuëh",
+ "ooui-toolbar-more": "Lom"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/af.json b/vendor/oojs/oojs-ui/i18n/af.json
new file mode 100644
index 00000000..6f79e370
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/af.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Naudefj"
+ ]
+ },
+ "ooui-outline-control-move-down": "Skuif item af",
+ "ooui-outline-control-move-up": "Skuif item op",
+ "ooui-outline-control-remove": "Verwyder item",
+ "ooui-toolbar-more": "Meer",
+ "ooui-toolgroup-expand": "Meer",
+ "ooui-toolgroup-collapse": "Minder",
+ "ooui-dialog-message-accept": "Regso",
+ "ooui-dialog-message-reject": "Kanselleer",
+ "ooui-dialog-process-error": "Iets het verkeerd gegaan",
+ "ooui-dialog-process-dismiss": "Sluit",
+ "ooui-dialog-process-retry": "Probeer weer",
+ "ooui-dialog-process-continue": "Gaan voort"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/am.json b/vendor/oojs/oojs-ui/i18n/am.json
new file mode 100644
index 00000000..bfe9d5c3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/am.json
@@ -0,0 +1,7 @@
+{
+ "@metadata": {
+ "authors": [
+ "Elfalem"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ar.json b/vendor/oojs/oojs-ui/i18n/ar.json
new file mode 100644
index 00000000..058a1491
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ar.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ciphers",
+ "Claw eg",
+ "Elfalem",
+ "Jdforrester",
+ "Mido",
+ "OsamaK",
+ "زكريا",
+ "مشعل الحربي",
+ "ترجمان05",
+ "Abanima"
+ ]
+ },
+ "ooui-outline-control-move-down": "انقل العنصر للأسفل",
+ "ooui-outline-control-move-up": "انقل العنصر للأعلى",
+ "ooui-outline-control-remove": "أزل العنصر",
+ "ooui-toolbar-more": "مزيد",
+ "ooui-toolgroup-expand": "مزيد",
+ "ooui-toolgroup-collapse": "أقل",
+ "ooui-dialog-message-accept": "موافق",
+ "ooui-dialog-message-reject": "ألغ",
+ "ooui-dialog-process-error": "حدث خطأ",
+ "ooui-dialog-process-dismiss": "أغلق",
+ "ooui-dialog-process-retry": "حاول مرة أخرى",
+ "ooui-dialog-process-continue": "استمر"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/arc.json b/vendor/oojs/oojs-ui/i18n/arc.json
new file mode 100644
index 00000000..de5b7aff
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/arc.json
@@ -0,0 +1,7 @@
+{
+ "@metadata": {
+ "authors": [
+ "Basharh"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/arq.json b/vendor/oojs/oojs-ui/i18n/arq.json
new file mode 100644
index 00000000..80987818
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/arq.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bachounda"
+ ]
+ },
+ "ooui-outline-control-move-down": "هبط الشيئ للتحت",
+ "ooui-outline-control-move-up": "طلع الشيئ للفوق",
+ "ooui-outline-control-remove": "أمحي العنصر",
+ "ooui-toolbar-more": "زيادة",
+ "ooui-dialog-message-accept": "مليح",
+ "ooui-dialog-message-reject": "رجَع",
+ "ooui-dialog-process-error": "حاجه ما خدمتش مليح",
+ "ooui-dialog-process-dismiss": "أرفضها",
+ "ooui-dialog-process-retry": "عاود جرب"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ast.json b/vendor/oojs/oojs-ui/i18n/ast.json
new file mode 100644
index 00000000..87d7688a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ast.json
@@ -0,0 +1,18 @@
+{
+ "@metadata": {
+ "authors": [
+ "Basharh",
+ "Bishnu Saikia",
+ "Xuacu"
+ ]
+ },
+ "ooui-outline-control-move-down": "Mover abaxo l'elementu",
+ "ooui-outline-control-move-up": "Mover arriba l'elementu",
+ "ooui-outline-control-remove": "Desaniciar elementu",
+ "ooui-toolbar-more": "Más",
+ "ooui-dialog-message-accept": "Aceutar",
+ "ooui-dialog-message-reject": "Encaboxar",
+ "ooui-dialog-process-error": "Daqué funcionó mal",
+ "ooui-dialog-process-dismiss": "Descartar",
+ "ooui-dialog-process-retry": "Vuelvi a intentalo"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/awa.json b/vendor/oojs/oojs-ui/i18n/awa.json
new file mode 100644
index 00000000..f78ed326
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/awa.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "1AnuraagPandey"
+ ]
+ },
+ "ooui-toolbar-more": "अउर"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/az.json b/vendor/oojs/oojs-ui/i18n/az.json
new file mode 100644
index 00000000..fc12d1b3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/az.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cekli829",
+ "Interfase",
+ "Jduranboger"
+ ]
+ },
+ "ooui-outline-control-move-down": "Bəndi aşağı apar",
+ "ooui-outline-control-move-up": "Bəndi yuxarı apar",
+ "ooui-outline-control-remove": "Bəndi sil",
+ "ooui-toolbar-more": "Daha artıq"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ba.json b/vendor/oojs/oojs-ui/i18n/ba.json
new file mode 100644
index 00000000..ff915b06
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ba.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "AiseluRB",
+ "Amire80",
+ "Assele",
+ "Haqmar",
+ "Sagan",
+ "Рустам Нурыев"
+ ]
+ },
+ "ooui-outline-control-move-down": "Аҫҡа күсерергә",
+ "ooui-outline-control-move-up": "Өҫкә күсерергә"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/bcc.json b/vendor/oojs/oojs-ui/i18n/bcc.json
new file mode 100644
index 00000000..a340a881
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/bcc.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Baloch Afghanistan"
+ ]
+ },
+ "ooui-dialog-message-accept": "اوکی",
+ "ooui-dialog-process-retry": "پدا کوشش کورتین"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/bcl.json b/vendor/oojs/oojs-ui/i18n/bcl.json
new file mode 100644
index 00000000..bc2251e8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/bcl.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Geopoet",
+ "Sky Harbor"
+ ]
+ },
+ "ooui-outline-control-move-down": "Balyuhon an aytem paibaba",
+ "ooui-outline-control-move-up": "Balyuhon an aytem paitaas",
+ "ooui-toolbar-more": "Kadugangan"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/be-tarask.json b/vendor/oojs/oojs-ui/i18n/be-tarask.json
new file mode 100644
index 00000000..c5475f85
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/be-tarask.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "EugeneZelenko",
+ "Wizardist",
+ "Чаховіч Уладзіслаў",
+ "Zedlik"
+ ]
+ },
+ "ooui-outline-control-move-down": "Перасунуць ніжэй",
+ "ooui-outline-control-move-up": "Перасунуць вышэй",
+ "ooui-toolbar-more": "Болей"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/be.json b/vendor/oojs/oojs-ui/i18n/be.json
new file mode 100644
index 00000000..fb0f6880
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/be.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Чаховіч Уладзіслаў",
+ "Artificial123"
+ ]
+ },
+ "ooui-dialog-message-accept": "ОК",
+ "ooui-dialog-message-reject": "Адмяніць"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/bg.json b/vendor/oojs/oojs-ui/i18n/bg.json
new file mode 100644
index 00000000..02d95b52
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/bg.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "DCLXVI",
+ "Hristofor.mirchev",
+ "පසිඳු කාවින්ද",
+ "Mitzev"
+ ]
+ },
+ "ooui-outline-control-remove": "Премахване на обекта",
+ "ooui-toolbar-more": "Още"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/bn.json b/vendor/oojs/oojs-ui/i18n/bn.json
new file mode 100644
index 00000000..1cfa6c45
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/bn.json
@@ -0,0 +1,26 @@
+{
+ "@metadata": {
+ "authors": [
+ "Aftab1995",
+ "Bellayet",
+ "Jayantanth",
+ "Nasir8891",
+ "Runab",
+ "Sayak Sarkar",
+ "Aftabuzzaman",
+ "RYasmeen (WMF)"
+ ]
+ },
+ "ooui-outline-control-move-down": "আইটেম নিচে স্থানান্তর",
+ "ooui-outline-control-move-up": "আইটেম উপরে স্থানান্তর",
+ "ooui-outline-control-remove": "আইটেম সরান",
+ "ooui-toolbar-more": "আরও",
+ "ooui-toolgroup-expand": "আরও",
+ "ooui-toolgroup-collapse": "কম দেখাও",
+ "ooui-dialog-message-accept": "ঠিক আছে",
+ "ooui-dialog-message-reject": "বাতিল",
+ "ooui-dialog-process-error": "কিছু একটায় ত্রুটি হয়েছে",
+ "ooui-dialog-process-dismiss": "বাতিল করুন",
+ "ooui-dialog-process-retry": "আবার চেষ্টা করুন",
+ "ooui-dialog-process-continue": "অগ্রসর হোন"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/br.json b/vendor/oojs/oojs-ui/i18n/br.json
new file mode 100644
index 00000000..83af863c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/br.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Fohanno",
+ "Fulup",
+ "Y-M D",
+ "Maoris"
+ ]
+ },
+ "ooui-outline-control-move-down": "Lakaat an elfenn da ziskenn",
+ "ooui-outline-control-move-up": "Lakaat an elfenn da bignat",
+ "ooui-outline-control-remove": "Tennañ an elfenn",
+ "ooui-toolbar-more": "Muioc'h",
+ "ooui-toolgroup-expand": "Muioc'h",
+ "ooui-toolgroup-collapse": "Nebeutoc'h",
+ "ooui-dialog-message-accept": "Mat eo",
+ "ooui-dialog-message-reject": "Nullañ",
+ "ooui-dialog-process-error": "Un dra bennak a-dreuz a zo bet",
+ "ooui-dialog-process-dismiss": "Disteurel",
+ "ooui-dialog-process-retry": "Klask en-dro",
+ "ooui-dialog-process-continue": "Kenderc'hel"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/bs.json b/vendor/oojs/oojs-ui/i18n/bs.json
new file mode 100644
index 00000000..130bd8e5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/bs.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "DzWiki"
+ ]
+ },
+ "ooui-outline-control-move-down": "Premjesti stavku dole",
+ "ooui-outline-control-move-up": "Premjesti stavku gore",
+ "ooui-outline-control-remove": "Ukloni stavku",
+ "ooui-toolbar-more": "Više",
+ "ooui-toolgroup-expand": "Više",
+ "ooui-toolgroup-collapse": "Manje",
+ "ooui-dialog-message-accept": "U redu",
+ "ooui-dialog-message-reject": "Otkaži",
+ "ooui-dialog-process-error": "Nešto je pošlo naopako",
+ "ooui-dialog-process-dismiss": "Odbaci",
+ "ooui-dialog-process-retry": "Pokušajte ponovo",
+ "ooui-dialog-process-continue": "Nastavi"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ca.json b/vendor/oojs/oojs-ui/i18n/ca.json
new file mode 100644
index 00000000..ce3afa43
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ca.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "Alvaro Vidal-Abarca",
+ "Amire80",
+ "Arnaugir",
+ "Pginer",
+ "QuimGil",
+ "SMP",
+ "Vriullop",
+ "Toniher",
+ "Edustus",
+ "Davidpar"
+ ]
+ },
+ "ooui-outline-control-move-down": "Baixa l'element",
+ "ooui-outline-control-move-up": "Puja l'element",
+ "ooui-outline-control-remove": "Esborra l'ítem",
+ "ooui-toolbar-more": "Més",
+ "ooui-toolgroup-expand": "Més",
+ "ooui-toolgroup-collapse": "Menys",
+ "ooui-dialog-message-accept": "D'acord",
+ "ooui-dialog-message-reject": "Cancel·la",
+ "ooui-dialog-process-error": "Alguna cosa no ha funcionat",
+ "ooui-dialog-process-dismiss": "Descarta",
+ "ooui-dialog-process-retry": "Torneu-ho a provar",
+ "ooui-dialog-process-continue": "Continua"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ce.json b/vendor/oojs/oojs-ui/i18n/ce.json
new file mode 100644
index 00000000..562dc3d5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ce.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amire80",
+ "Умар"
+ ]
+ },
+ "ooui-outline-control-move-down": "Лаха яккха элемент",
+ "ooui-outline-control-move-up": "Лаккха яккха элемент",
+ "ooui-outline-control-remove": "ДӀадаха меттиг",
+ "ooui-toolbar-more": "Кхин",
+ "ooui-toolgroup-expand": "Дукха",
+ "ooui-toolgroup-collapse": "КӀезиг",
+ "ooui-dialog-message-accept": "ХӀаъ",
+ "ooui-dialog-message-reject": "Цаоьшу",
+ "ooui-dialog-process-continue": "Кхин дӀа"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ckb.json b/vendor/oojs/oojs-ui/i18n/ckb.json
new file mode 100644
index 00000000..0c66619d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ckb.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Calak",
+ "Muhammed taha",
+ "Serwan"
+ ]
+ },
+ "ooui-dialog-message-accept": "باشە",
+ "ooui-dialog-message-reject": "پاشگەزبوونەوە"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/co.json b/vendor/oojs/oojs-ui/i18n/co.json
new file mode 100644
index 00000000..01d181d7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/co.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Paulu"
+ ]
+ },
+ "ooui-outline-control-move-down": "Fà falà l'ogettu",
+ "ooui-outline-control-move-up": "Fà cullà l'ogettu"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/crh-cyrl.json b/vendor/oojs/oojs-ui/i18n/crh-cyrl.json
new file mode 100644
index 00000000..ccc00269
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/crh-cyrl.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Don Alessandro"
+ ]
+ },
+ "ooui-toolbar-more": "Даа зияде"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/crh-latn.json b/vendor/oojs/oojs-ui/i18n/crh-latn.json
new file mode 100644
index 00000000..7ad7b0bb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/crh-latn.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Don Alessandro"
+ ]
+ },
+ "ooui-toolbar-more": "Daa ziyade"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/cs.json b/vendor/oojs/oojs-ui/i18n/cs.json
new file mode 100644
index 00000000..1db9aed5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/cs.json
@@ -0,0 +1,29 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chmee2",
+ "Jkjk",
+ "Juandev",
+ "Koo6",
+ "Littledogboy",
+ "Michaelbrabec",
+ "Mormegil",
+ "Polda18",
+ "Tchoř",
+ "ශ්වෙත",
+ "Vojtěch Dostál"
+ ]
+ },
+ "ooui-outline-control-move-down": "Přesunout položku dolů",
+ "ooui-outline-control-move-up": "Přesunout položku nahoru",
+ "ooui-outline-control-remove": "Odstranit položku",
+ "ooui-toolbar-more": "Další",
+ "ooui-toolgroup-expand": "Více",
+ "ooui-toolgroup-collapse": "Méně",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Storno",
+ "ooui-dialog-process-error": "Něco se pokazilo",
+ "ooui-dialog-process-dismiss": "Zavřít",
+ "ooui-dialog-process-retry": "Zkusit znovu",
+ "ooui-dialog-process-continue": "Pokračovat"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/cu.json b/vendor/oojs/oojs-ui/i18n/cu.json
new file mode 100644
index 00000000..aa916af0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/cu.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "ОйЛ"
+ ]
+ },
+ "ooui-toolbar-more": "вѧщє",
+ "ooui-toolgroup-expand": "вѧщє"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/cy.json b/vendor/oojs/oojs-ui/i18n/cy.json
new file mode 100644
index 00000000..b74cd064
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/cy.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lloffiwr",
+ "Robin Owain",
+ "ОйЛ",
+ "DChan (WMF)"
+ ]
+ },
+ "ooui-outline-control-move-down": "Symud yr eitem i lawr",
+ "ooui-outline-control-move-up": "Symud yr eitem i fyny",
+ "ooui-outline-control-remove": "Tynnu'r eitem",
+ "ooui-toolbar-more": "Rhagor"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/da.json b/vendor/oojs/oojs-ui/i18n/da.json
new file mode 100644
index 00000000..0b847be1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/da.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cgtdk",
+ "Christian List",
+ "EileenSanda",
+ "Laketown",
+ "Palnatoke",
+ "Simeondahl",
+ "Tehnix"
+ ]
+ },
+ "ooui-outline-control-move-down": "Flyt ned",
+ "ooui-outline-control-move-up": "Flyt op",
+ "ooui-toolbar-more": "Mere"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/de.json b/vendor/oojs/oojs-ui/i18n/de.json
new file mode 100644
index 00000000..15624fd4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/de.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "APPER",
+ "G.Hagedorn",
+ "Inkowik",
+ "Jcornelius",
+ "Jdforrester",
+ "Kghbln",
+ "Metalhead64",
+ "Murma174",
+ "Se4598",
+ "Tomabrafix"
+ ]
+ },
+ "ooui-outline-control-move-down": "Element nach unten verschieben",
+ "ooui-outline-control-move-up": "Element nach oben verschieben",
+ "ooui-outline-control-remove": "Element entfernen",
+ "ooui-toolbar-more": "Mehr",
+ "ooui-toolgroup-expand": "Mehr",
+ "ooui-toolgroup-collapse": "Weniger",
+ "ooui-dialog-message-accept": "Okay",
+ "ooui-dialog-message-reject": "Abbrechen",
+ "ooui-dialog-process-error": "Etwas ist schief gelaufen",
+ "ooui-dialog-process-dismiss": "Ausblenden",
+ "ooui-dialog-process-retry": "Erneut versuchen",
+ "ooui-dialog-process-continue": "Fortfahren"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/diq.json b/vendor/oojs/oojs-ui/i18n/diq.json
new file mode 100644
index 00000000..881ff674
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/diq.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Erdemaslancan",
+ "Gorizon",
+ "Kghbln",
+ "Marmase",
+ "Mirzali",
+ "Se4598"
+ ]
+ },
+ "ooui-outline-control-move-down": "Bendi bere cêr",
+ "ooui-outline-control-move-up": "Bendi bere cor",
+ "ooui-outline-control-remove": "Obcey wedare",
+ "ooui-toolbar-more": "Zewbi"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/dsb.json b/vendor/oojs/oojs-ui/i18n/dsb.json
new file mode 100644
index 00000000..7ad3f200
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/dsb.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Michawiki"
+ ]
+ },
+ "ooui-outline-control-move-down": "Element dołoj pśesunuś",
+ "ooui-outline-control-move-up": "Element górjej pśesunuś",
+ "ooui-outline-control-remove": "Zapisk wótpóraś",
+ "ooui-toolbar-more": "Wěcej"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/egl.json b/vendor/oojs/oojs-ui/i18n/egl.json
new file mode 100644
index 00000000..624ecaa3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/egl.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lévi",
+ "Gloria sah"
+ ]
+ },
+ "ooui-outline-control-move-down": "Spôsta in bâs",
+ "ooui-outline-control-move-up": "Spôsta in êlt",
+ "ooui-outline-control-remove": "Armōv l'elemèint",
+ "ooui-toolbar-more": "Êter",
+ "ooui-dialog-message-accept": "'D acòrdi",
+ "ooui-dialog-message-reject": "Scanślèr"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/el.json b/vendor/oojs/oojs-ui/i18n/el.json
new file mode 100644
index 00000000..6fb7dbad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/el.json
@@ -0,0 +1,26 @@
+{
+ "@metadata": {
+ "authors": [
+ "Astralnet",
+ "Dipa1965",
+ "Evropi",
+ "FocalPoint",
+ "Geraki",
+ "Glavkos",
+ "Nikosguard",
+ "Tifa93"
+ ]
+ },
+ "ooui-outline-control-move-down": "Μετακίνηση στοιχείου προς τα κάτω",
+ "ooui-outline-control-move-up": "Μετακίνηση στοιχείου προς τα επάνω",
+ "ooui-outline-control-remove": "Αφαίρεση στοιχείου",
+ "ooui-toolbar-more": "Περισσότερα",
+ "ooui-toolgroup-expand": "Περισσότερα",
+ "ooui-toolgroup-collapse": "Λιγότερα",
+ "ooui-dialog-message-accept": "ΟΚ",
+ "ooui-dialog-message-reject": "Ακύρωση",
+ "ooui-dialog-process-error": "Κάτι πήγε στραβά",
+ "ooui-dialog-process-dismiss": "Απόρριψη",
+ "ooui-dialog-process-retry": "Δοκιμάστε ξανά",
+ "ooui-dialog-process-continue": "Συνέχεια"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/eml.json b/vendor/oojs/oojs-ui/i18n/eml.json
new file mode 100644
index 00000000..6d9e8bf0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/eml.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gloria sah",
+ "Lévi"
+ ]
+ },
+ "ooui-outline-control-move-down": "Spôsta in bâs",
+ "ooui-outline-control-move-up": "Spôsta in êlta",
+ "ooui-outline-control-remove": "Tór vìa 'l elemèint",
+ "ooui-toolbar-more": "Êter",
+ "ooui-dialog-message-accept": "'D acòrdi",
+ "ooui-dialog-message-reject": "Scanślèr"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/en.json b/vendor/oojs/oojs-ui/i18n/en.json
new file mode 100644
index 00000000..1db3fd85
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/en.json
@@ -0,0 +1,31 @@
+{
+ "@metadata": {
+ "authors": [
+ "Trevor Parscal",
+ "Ed Sanders",
+ "James D. Forrester",
+ "Raimond Spekking",
+ "Erik Moeller",
+ "Moriel Schottlender",
+ "Yuki Shira",
+ "Siebrand Mazeland",
+ "Rob Moen",
+ "Timo Tijhof",
+ "Roan Kattouw",
+ "Christian Williams",
+ "Amir E. Aharoni"
+ ]
+ },
+ "ooui-outline-control-move-down": "Move item down",
+ "ooui-outline-control-move-up": "Move item up",
+ "ooui-outline-control-remove": "Remove item",
+ "ooui-toolbar-more": "More",
+ "ooui-toolgroup-expand": "More",
+ "ooui-toolgroup-collapse": "Fewer",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Cancel",
+ "ooui-dialog-process-error": "Something went wrong",
+ "ooui-dialog-process-dismiss": "Dismiss",
+ "ooui-dialog-process-retry": "Try again",
+ "ooui-dialog-process-continue": "Continue"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/eo.json b/vendor/oojs/oojs-ui/i18n/eo.json
new file mode 100644
index 00000000..8d9714c6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/eo.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Happy5214",
+ "KuboF",
+ "Shirayuki",
+ "Yekrats"
+ ]
+ },
+ "ooui-outline-control-move-down": "Movi eron suben",
+ "ooui-outline-control-move-up": "Movi eron supren",
+ "ooui-toolbar-more": "Pli"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/es.json b/vendor/oojs/oojs-ui/i18n/es.json
new file mode 100644
index 00000000..915791e6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/es.json
@@ -0,0 +1,33 @@
+{
+ "@metadata": {
+ "authors": [
+ "Armando-Martin",
+ "Aruizdr",
+ "Benfutbol10",
+ "DJ Nietzsche",
+ "Erdemaslancan",
+ "Fitoschido",
+ "Imre",
+ "Invadinado",
+ "Jdforrester",
+ "Jduranboger",
+ "PoLuX124",
+ "Ralgis",
+ "Thehelpfulone",
+ "Gloria sah",
+ "Macofe"
+ ]
+ },
+ "ooui-outline-control-move-down": "Bajar elemento",
+ "ooui-outline-control-move-up": "Subir elemento",
+ "ooui-outline-control-remove": "Eliminar elemento",
+ "ooui-toolbar-more": "Más",
+ "ooui-toolgroup-expand": "Más",
+ "ooui-toolgroup-collapse": "Menos",
+ "ooui-dialog-message-accept": "Aceptar",
+ "ooui-dialog-message-reject": "Cancelar",
+ "ooui-dialog-process-error": "Algo salió mal",
+ "ooui-dialog-process-dismiss": "Descartar",
+ "ooui-dialog-process-retry": "Intentar de nuevo",
+ "ooui-dialog-process-continue": "Continuar"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/et.json b/vendor/oojs/oojs-ui/i18n/et.json
new file mode 100644
index 00000000..6a212b6b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/et.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Avjoska",
+ "Pikne"
+ ]
+ },
+ "ooui-outline-control-move-down": "Liiguta üksust allapoole",
+ "ooui-outline-control-move-up": "Liiguta üksust ülespoole",
+ "ooui-outline-control-remove": "Eemalda üksus",
+ "ooui-toolbar-more": "Veel",
+ "ooui-toolgroup-expand": "Veel",
+ "ooui-toolgroup-collapse": "Vähem",
+ "ooui-dialog-message-accept": "Sobib",
+ "ooui-dialog-message-reject": "Loobu",
+ "ooui-dialog-process-error": "Midagi läks valesti",
+ "ooui-dialog-process-dismiss": "Hülga",
+ "ooui-dialog-process-retry": "Proovi uuesti",
+ "ooui-dialog-process-continue": "Jätka"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/eu.json b/vendor/oojs/oojs-ui/i18n/eu.json
new file mode 100644
index 00000000..e947582d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/eu.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "An13sa",
+ "Unai Fdz. de Betoño",
+ "Xabier Armendaritz",
+ "Subi"
+ ]
+ },
+ "ooui-outline-control-move-down": "Mugitu itema beherantz",
+ "ooui-outline-control-move-up": "Mugitu itema gorantz",
+ "ooui-toolbar-more": "Gehiago",
+ "ooui-toolgroup-expand": "Gehiago",
+ "ooui-toolgroup-collapse": "Gutxiago",
+ "ooui-dialog-message-accept": "Ados",
+ "ooui-dialog-message-reject": "Utzi",
+ "ooui-dialog-process-error": "Zerbaitek huts egin du",
+ "ooui-dialog-process-retry": "Saiatu berriro",
+ "ooui-dialog-process-continue": "Jarraitu"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/fa.json b/vendor/oojs/oojs-ui/i18n/fa.json
new file mode 100644
index 00000000..7cfcfa21
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/fa.json
@@ -0,0 +1,29 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dalba",
+ "Ebraminio",
+ "Jdforrester",
+ "Ladsgroup",
+ "Mjbmr",
+ "Nojan Madinehi",
+ "Reza1615",
+ "Taha",
+ "درفش کاویانی",
+ "Armin1392",
+ "Alirezaaa"
+ ]
+ },
+ "ooui-outline-control-move-down": "انتقال مورد به پایین",
+ "ooui-outline-control-move-up": "انتقال مورد به بالا",
+ "ooui-outline-control-remove": "حذف مورد",
+ "ooui-toolbar-more": "بیشتر",
+ "ooui-toolgroup-expand": "بیشتر",
+ "ooui-toolgroup-collapse": "کمتر",
+ "ooui-dialog-message-accept": "تأیید",
+ "ooui-dialog-message-reject": "لغو",
+ "ooui-dialog-process-error": "مشکلی وجود دارد",
+ "ooui-dialog-process-dismiss": "نپذیرفتن",
+ "ooui-dialog-process-retry": "دوباره امتحان کن",
+ "ooui-dialog-process-continue": "ادامه"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/fi.json b/vendor/oojs/oojs-ui/i18n/fi.json
new file mode 100644
index 00000000..3fb4110c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/fi.json
@@ -0,0 +1,31 @@
+{
+ "@metadata": {
+ "authors": [
+ "Beluga",
+ "Crt",
+ "Harriv",
+ "Linnea",
+ "Nedergard",
+ "Nike",
+ "Olli",
+ "Pxos",
+ "Samoasambia",
+ "Silvonen",
+ "Skalman",
+ "Stryn",
+ "VezonThunder"
+ ]
+ },
+ "ooui-outline-control-move-down": "Siirrä kohdetta alaspäin",
+ "ooui-outline-control-move-up": "Siirrä kohdetta ylöspäin",
+ "ooui-outline-control-remove": "Poista kohde",
+ "ooui-toolbar-more": "Lisää",
+ "ooui-toolgroup-expand": "Enemmän",
+ "ooui-toolgroup-collapse": "Vähemmän",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Peruuta",
+ "ooui-dialog-process-error": "Jokin meni pieleen",
+ "ooui-dialog-process-dismiss": "Hylkää",
+ "ooui-dialog-process-retry": "Yritä uudelleen",
+ "ooui-dialog-process-continue": "Jatka"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/fo.json b/vendor/oojs/oojs-ui/i18n/fo.json
new file mode 100644
index 00000000..6230cc9b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/fo.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "EileenSanda"
+ ]
+ },
+ "ooui-outline-control-move-down": "Flyt lutin niður",
+ "ooui-outline-control-move-up": "Flyt lutin upp",
+ "ooui-outline-control-remove": "Tak ein lut burtur",
+ "ooui-toolbar-more": "Meira",
+ "ooui-toolgroup-expand": "Meira",
+ "ooui-toolgroup-collapse": "Færri",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Avbrót",
+ "ooui-dialog-process-error": "Okkurt gekk galið",
+ "ooui-dialog-process-dismiss": "Lat aftur",
+ "ooui-dialog-process-retry": "Royn aftur",
+ "ooui-dialog-process-continue": "Halt fram"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/fr.json b/vendor/oojs/oojs-ui/i18n/fr.json
new file mode 100644
index 00000000..9144cb01
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/fr.json
@@ -0,0 +1,45 @@
+{
+ "@metadata": {
+ "authors": [
+ "Automatik",
+ "Benoit Rochon",
+ "Boniface",
+ "Brunoperel",
+ "Crochet.david",
+ "DavidL",
+ "Dereckson",
+ "Gomoko",
+ "Guillom",
+ "Hello71",
+ "Jean-Frédéric",
+ "Linedwell",
+ "Ltrlg",
+ "Metroitendo",
+ "NemesisIII",
+ "Nicolas NALLET",
+ "Npettiaux",
+ "Rastus Vernon",
+ "Seb35",
+ "Sherbrooke",
+ "Tpt",
+ "Trizek",
+ "Urhixidur",
+ "Verdy p",
+ "Wyz",
+ "SnowedEarth",
+ "Jdforrester"
+ ]
+ },
+ "ooui-outline-control-move-down": "Faire descendre l’élément",
+ "ooui-outline-control-move-up": "Faire monter l’élément",
+ "ooui-outline-control-remove": "Supprimer l’élément",
+ "ooui-toolbar-more": "Plus",
+ "ooui-toolgroup-expand": "Plus",
+ "ooui-toolgroup-collapse": "Moins",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Annuler",
+ "ooui-dialog-process-error": "Quelque chose a mal tourné",
+ "ooui-dialog-process-dismiss": "Rejeter",
+ "ooui-dialog-process-retry": "Réessayez",
+ "ooui-dialog-process-continue": "Continuer"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/frr.json b/vendor/oojs/oojs-ui/i18n/frr.json
new file mode 100644
index 00000000..54d0fb22
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/frr.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "ChrisPtDe",
+ "Murma174"
+ ]
+ },
+ "ooui-outline-control-move-down": "Element efter onern sküüw",
+ "ooui-outline-control-move-up": "Element efter boowen sküüw",
+ "ooui-outline-control-remove": "Element wechnem",
+ "ooui-toolbar-more": "Muar"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/fur.json b/vendor/oojs/oojs-ui/i18n/fur.json
new file mode 100644
index 00000000..83c2fd9e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/fur.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Klenje",
+ "Tocaibon"
+ ]
+ },
+ "ooui-outline-control-move-down": "sposte sot",
+ "ooui-outline-control-move-up": "sposte in su",
+ "ooui-toolbar-more": "Altri"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/fy.json b/vendor/oojs/oojs-ui/i18n/fy.json
new file mode 100644
index 00000000..ddf9ff75
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/fy.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Robin0van0der0vliet"
+ ]
+ },
+ "ooui-toolbar-more": "Mear",
+ "ooui-toolgroup-expand": "Mear",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Annulearje"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/gd.json b/vendor/oojs/oojs-ui/i18n/gd.json
new file mode 100644
index 00000000..6a83c9c0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/gd.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "GunChleoc"
+ ]
+ },
+ "ooui-outline-control-move-down": "Gluais nì sìos",
+ "ooui-outline-control-move-up": "Gluais nì suas",
+ "ooui-outline-control-remove": "Thoir air falbh an nì",
+ "ooui-toolbar-more": "Barrachd",
+ "ooui-dialog-message-accept": "Ceart ma-thà",
+ "ooui-dialog-message-reject": "Sguir dheth"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/gl.json b/vendor/oojs/oojs-ui/i18n/gl.json
new file mode 100644
index 00000000..a4339f47
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/gl.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Alison",
+ "Kscanne",
+ "Toliño",
+ "Elisardojm"
+ ]
+ },
+ "ooui-outline-control-move-down": "Mover o elemento abaixo",
+ "ooui-outline-control-move-up": "Mover o elemento arriba",
+ "ooui-outline-control-remove": "Eliminar o elemento",
+ "ooui-toolbar-more": "Máis",
+ "ooui-toolgroup-expand": "Máis",
+ "ooui-toolgroup-collapse": "Menos",
+ "ooui-dialog-message-accept": "Aceptar",
+ "ooui-dialog-message-reject": "Cancelar",
+ "ooui-dialog-process-error": "Algo foi mal",
+ "ooui-dialog-process-dismiss": "Agochar",
+ "ooui-dialog-process-retry": "Inténteo de novo",
+ "ooui-dialog-process-continue": "Continuar"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/gu.json b/vendor/oojs/oojs-ui/i18n/gu.json
new file mode 100644
index 00000000..2d8315bf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/gu.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ashok modhvadia",
+ "KartikMistry",
+ "The Discoverer"
+ ]
+ },
+ "ooui-outline-control-move-down": "વસ્તુ નીચે ખસેડો",
+ "ooui-outline-control-move-up": "વસ્તુ ઉપર ખસેડો",
+ "ooui-outline-control-remove": "વસ્તુ હટાવો",
+ "ooui-toolbar-more": "વધુ",
+ "ooui-dialog-message-accept": "બરાબર",
+ "ooui-dialog-message-reject": "રદ કરો",
+ "ooui-dialog-process-error": "કંઇક ગરબડ થઇ",
+ "ooui-dialog-process-retry": "ફરી પ્રયત્ન કરો"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/he.json b/vendor/oojs/oojs-ui/i18n/he.json
new file mode 100644
index 00000000..cadc416c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/he.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amire80",
+ "ExampleTomer",
+ "Guycn2",
+ "Matanya",
+ "Mooeypoo",
+ "Orsa",
+ "Shimmin Beg",
+ "אור שפירא",
+ "חיים",
+ "ערן",
+ "פוילישער",
+ "קיפודנחש"
+ ]
+ },
+ "ooui-outline-control-move-down": "להזיז את הפריט מטה",
+ "ooui-outline-control-move-up": "להזיז את הפריט מעלה",
+ "ooui-outline-control-remove": "להסיר את הפריט",
+ "ooui-toolbar-more": "עוד",
+ "ooui-toolgroup-expand": "יותר",
+ "ooui-toolgroup-collapse": "פחות",
+ "ooui-dialog-message-accept": "אישור",
+ "ooui-dialog-message-reject": "ביטול",
+ "ooui-dialog-process-error": "משהו השתבש",
+ "ooui-dialog-process-dismiss": "לוותר",
+ "ooui-dialog-process-retry": "לנסות שוב",
+ "ooui-dialog-process-continue": "המשך"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/hi.json b/vendor/oojs/oojs-ui/i18n/hi.json
new file mode 100644
index 00000000..ce86aaab
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/hi.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ansumang",
+ "Devayon",
+ "Rajesh",
+ "Siddhartha Ghai",
+ "Goelujjwal",
+ "Ankita-ks"
+ ]
+ },
+ "ooui-outline-control-move-down": "प्रविष्टि नीचे ले जाएँ",
+ "ooui-outline-control-move-up": "प्रविष्टि ऊपर ले जाएँ",
+ "ooui-outline-control-remove": "आइटम हटाएँ",
+ "ooui-toolbar-more": "अधिक",
+ "ooui-toolgroup-expand": "अधिक",
+ "ooui-toolgroup-collapse": "कम",
+ "ooui-dialog-message-accept": "ठीक है",
+ "ooui-dialog-message-reject": "रद्द करें",
+ "ooui-dialog-process-error": "कुछ गलत हुअा है",
+ "ooui-dialog-process-dismiss": "ख़ारिज करें",
+ "ooui-dialog-process-retry": "पुनः प्रयास करें",
+ "ooui-dialog-process-continue": "जारी रखें"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/hr.json b/vendor/oojs/oojs-ui/i18n/hr.json
new file mode 100644
index 00000000..91188984
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/hr.json
@@ -0,0 +1,21 @@
+{
+ "@metadata": {
+ "authors": [
+ "MaGa",
+ "Roberta F.",
+ "SpeedyGonsales",
+ "Zeljko.filipin"
+ ]
+ },
+ "ooui-outline-control-move-down": "Premjesti stavku dolje",
+ "ooui-outline-control-move-up": "Premjesti stavku gore",
+ "ooui-outline-control-remove": "Ukloni",
+ "ooui-toolbar-more": "Više",
+ "ooui-toolgroup-expand": "Više",
+ "ooui-toolgroup-collapse": "Manje",
+ "ooui-dialog-message-accept": "U redu",
+ "ooui-dialog-message-reject": "Odustani",
+ "ooui-dialog-process-error": "Nešto je pošlo po zlu",
+ "ooui-dialog-process-dismiss": "Zatvori",
+ "ooui-dialog-process-retry": "Pokušajte ponovo"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/hsb.json b/vendor/oojs/oojs-ui/i18n/hsb.json
new file mode 100644
index 00000000..00894e4e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/hsb.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "J budissin",
+ "Michawiki"
+ ]
+ },
+ "ooui-outline-control-move-down": "Zapisk dele přesunyć",
+ "ooui-outline-control-move-up": "Zapisk horje přesunyć",
+ "ooui-outline-control-remove": "Zapisk wotstronić",
+ "ooui-toolbar-more": "Wjace",
+ "ooui-toolgroup-expand": "Wjace",
+ "ooui-toolgroup-collapse": "Mjenje",
+ "ooui-dialog-message-accept": "W porjadku",
+ "ooui-dialog-message-reject": "Přetorhnyć",
+ "ooui-dialog-process-error": "Něšto je so nimokuliło",
+ "ooui-dialog-process-dismiss": "Schować",
+ "ooui-dialog-process-retry": "Hišće raz spytać",
+ "ooui-dialog-process-continue": "Dale"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/hu.json b/vendor/oojs/oojs-ui/i18n/hu.json
new file mode 100644
index 00000000..d50e62da
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/hu.json
@@ -0,0 +1,23 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dj",
+ "Einstein2",
+ "Misibacsi",
+ "ViDam",
+ "Tacsipacsi",
+ "Csega"
+ ]
+ },
+ "ooui-outline-control-move-down": "Elem mozgatása lefelé",
+ "ooui-outline-control-move-up": "Elem mozgatása felfelé",
+ "ooui-outline-control-remove": "Elem eltávolítása",
+ "ooui-toolbar-more": "Tovább...",
+ "ooui-toolgroup-expand": "Több",
+ "ooui-toolgroup-collapse": "Kevesebb",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Mégse",
+ "ooui-dialog-process-dismiss": "Elrejt",
+ "ooui-dialog-process-retry": "Próbáld újra",
+ "ooui-dialog-process-continue": "Folytatás"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/hy.json b/vendor/oojs/oojs-ui/i18n/hy.json
new file mode 100644
index 00000000..2aaf4e46
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/hy.json
@@ -0,0 +1,21 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vacio",
+ "Xelgen",
+ "Դավիթ Սարոյան"
+ ]
+ },
+ "ooui-outline-control-move-down": "Իջեցնել կետը",
+ "ooui-outline-control-move-up": "Բարձրացնել կետը",
+ "ooui-outline-control-remove": "Հեռացնել տարրը",
+ "ooui-toolbar-more": "Ավելին",
+ "ooui-toolgroup-expand": "Ավելին",
+ "ooui-toolgroup-collapse": "Պակաս",
+ "ooui-dialog-message-accept": "Լավ",
+ "ooui-dialog-message-reject": "Չեղարկել",
+ "ooui-dialog-process-error": "Ինչ-որ սխալ է տեղի ունեցել",
+ "ooui-dialog-process-dismiss": "Փակել",
+ "ooui-dialog-process-retry": "Կրկին փորձել",
+ "ooui-dialog-process-continue": "Շարունակել"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ia.json b/vendor/oojs/oojs-ui/i18n/ia.json
new file mode 100644
index 00000000..b374b6f6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ia.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "McDutchie"
+ ]
+ },
+ "ooui-outline-control-move-down": "Displaciar elemento in basso",
+ "ooui-outline-control-move-up": "Displaciar elemento in alto",
+ "ooui-outline-control-remove": "Remover elemento",
+ "ooui-toolbar-more": "Plus",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Cancellar",
+ "ooui-dialog-process-error": "Qualcosa ha vadite mal",
+ "ooui-dialog-process-dismiss": "Clauder",
+ "ooui-dialog-process-retry": "Reprobar"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/id.json b/vendor/oojs/oojs-ui/i18n/id.json
new file mode 100644
index 00000000..bd65e71a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/id.json
@@ -0,0 +1,26 @@
+{
+ "@metadata": {
+ "authors": [
+ "Farras",
+ "Ilham151096",
+ "Iwan Novirion",
+ "Iyan",
+ "Kenrick95",
+ "McDutchie",
+ "Rv77ax",
+ "William Surya Permana"
+ ]
+ },
+ "ooui-outline-control-move-down": "Pindahkan butir ke bawah",
+ "ooui-outline-control-move-up": "Pindahkan butir ke atas",
+ "ooui-outline-control-remove": "Hapus butir",
+ "ooui-toolbar-more": "Lainnya",
+ "ooui-toolgroup-expand": "Selengkapnya",
+ "ooui-toolgroup-collapse": "Secukupnya",
+ "ooui-dialog-message-accept": "Oke",
+ "ooui-dialog-message-reject": "Batal",
+ "ooui-dialog-process-error": "Ada yang tidak beres",
+ "ooui-dialog-process-dismiss": "Tutup",
+ "ooui-dialog-process-retry": "Coba lagi",
+ "ooui-dialog-process-continue": "Lanjutkan"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ie.json b/vendor/oojs/oojs-ui/i18n/ie.json
new file mode 100644
index 00000000..241cc331
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ie.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Makuba"
+ ]
+ },
+ "ooui-outline-control-move-down": "Mover element a infra",
+ "ooui-outline-control-move-up": "Mover element a supra",
+ "ooui-toolbar-more": "Plu"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ilo.json b/vendor/oojs/oojs-ui/i18n/ilo.json
new file mode 100644
index 00000000..b37beae1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ilo.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lam-ang"
+ ]
+ },
+ "ooui-outline-control-move-down": "Ipababa ti banag",
+ "ooui-outline-control-move-up": "Ipangato ti banag",
+ "ooui-outline-control-remove": "Ikkaten ti banag",
+ "ooui-toolbar-more": "Adu pay",
+ "ooui-toolgroup-expand": "Adu pay",
+ "ooui-toolgroup-collapse": "Basbassit",
+ "ooui-dialog-message-accept": "Sige",
+ "ooui-dialog-message-reject": "Ukasen",
+ "ooui-dialog-process-error": "Adda madi a napasamak",
+ "ooui-dialog-process-dismiss": "Pugsayen",
+ "ooui-dialog-process-retry": "Padasen manen",
+ "ooui-dialog-process-continue": "Agtuloy"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/is.json b/vendor/oojs/oojs-ui/i18n/is.json
new file mode 100644
index 00000000..3a4e1454
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/is.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Maxí",
+ "Snævar"
+ ]
+ },
+ "ooui-outline-control-move-down": "Færa atriði niður",
+ "ooui-outline-control-move-up": "Færa atriði upp",
+ "ooui-outline-control-remove": "Fjarlægja atriði",
+ "ooui-toolbar-more": "Fleira",
+ "ooui-toolgroup-expand": "Fleira",
+ "ooui-toolgroup-collapse": "Færra",
+ "ooui-dialog-message-accept": "Í lagi",
+ "ooui-dialog-message-reject": "Hætta við",
+ "ooui-dialog-process-error": "Eitthvað mistókst",
+ "ooui-dialog-process-dismiss": "Loka",
+ "ooui-dialog-process-retry": "Reyna aftur",
+ "ooui-dialog-process-continue": "Halda áfram"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/it.json b/vendor/oojs/oojs-ui/i18n/it.json
new file mode 100644
index 00000000..0ff8af8f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/it.json
@@ -0,0 +1,30 @@
+{
+ "@metadata": {
+ "authors": [
+ "Beta16",
+ "Darth Kule",
+ "Doc.mari",
+ "Eleonora negri",
+ "Elitre",
+ "F. Cosoleto",
+ "FRacco",
+ "Gianfranco",
+ "Minerva Titani",
+ "Raoli",
+ "Una giornata uggiosa '94",
+ "Ontsed"
+ ]
+ },
+ "ooui-outline-control-move-down": "Sposta in basso",
+ "ooui-outline-control-move-up": "Sposta in alto",
+ "ooui-outline-control-remove": "Rimuovi elemento",
+ "ooui-toolbar-more": "Altro",
+ "ooui-toolgroup-expand": "Più",
+ "ooui-toolgroup-collapse": "Meno",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Annulla",
+ "ooui-dialog-process-error": "Qualcosa è andato storto",
+ "ooui-dialog-process-dismiss": "Nascondi",
+ "ooui-dialog-process-retry": "Riprova",
+ "ooui-dialog-process-continue": "Continua"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ja.json b/vendor/oojs/oojs-ui/i18n/ja.json
new file mode 100644
index 00000000..ec86124e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ja.json
@@ -0,0 +1,24 @@
+{
+ "@metadata": {
+ "authors": [
+ "Fryed-peach",
+ "Miya",
+ "Penn Station",
+ "Shirayuki",
+ "Takot",
+ "Los688"
+ ]
+ },
+ "ooui-outline-control-move-down": "項目を下に移動させる",
+ "ooui-outline-control-move-up": "項目を上に移動させる",
+ "ooui-outline-control-remove": "項目を除去",
+ "ooui-toolbar-more": "その他",
+ "ooui-toolgroup-expand": "続き",
+ "ooui-toolgroup-collapse": "折り畳む",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "キャンセル",
+ "ooui-dialog-process-error": "エラーが発生しました…",
+ "ooui-dialog-process-dismiss": "閉じる",
+ "ooui-dialog-process-retry": "もう一度お試しください",
+ "ooui-dialog-process-continue": "続行"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/jv.json b/vendor/oojs/oojs-ui/i18n/jv.json
new file mode 100644
index 00000000..8827af38
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/jv.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gleki",
+ "NoiX180",
+ "Pras"
+ ]
+ },
+ "ooui-outline-control-move-down": "Pindhahaken butir mangandhap"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ka.json b/vendor/oojs/oojs-ui/i18n/ka.json
new file mode 100644
index 00000000..60ef661b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ka.json
@@ -0,0 +1,26 @@
+{
+ "@metadata": {
+ "authors": [
+ "BRUTE",
+ "David1010",
+ "Gleki",
+ "ITshnik",
+ "MIKHEIL",
+ "NoiX180",
+ "Pras",
+ "Tokoko"
+ ]
+ },
+ "ooui-outline-control-move-down": "ელემენტის ქვემოთ გადატანა",
+ "ooui-outline-control-move-up": "ელემენტის ზემოთ გადატანა",
+ "ooui-outline-control-remove": "წაშლა",
+ "ooui-toolbar-more": "მეტი",
+ "ooui-toolgroup-expand": "მეტი",
+ "ooui-toolgroup-collapse": "რამდენიმე",
+ "ooui-dialog-message-accept": "კარგი",
+ "ooui-dialog-message-reject": "გაუქმება",
+ "ooui-dialog-process-error": "მოხდა რაღაც შეცდომა",
+ "ooui-dialog-process-dismiss": "დამალვა",
+ "ooui-dialog-process-retry": "კიდევ სცადეთ",
+ "ooui-dialog-process-continue": "გაგრძელება"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/kk-cyrl.json b/vendor/oojs/oojs-ui/i18n/kk-cyrl.json
new file mode 100644
index 00000000..1d7317b2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/kk-cyrl.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Arystanbek"
+ ]
+ },
+ "ooui-outline-control-move-down": "Элементті төмен жылжыту",
+ "ooui-outline-control-move-up": "Элементті жоғары жылжыту",
+ "ooui-outline-control-remove": "Элементті алып тастау",
+ "ooui-toolbar-more": "толығырақ",
+ "ooui-toolgroup-expand": "Тағы",
+ "ooui-toolgroup-collapse": "Азырақ",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Қажет емес",
+ "ooui-dialog-process-error": "Бірдеңеден қате кетті",
+ "ooui-dialog-process-dismiss": "Тоқтату",
+ "ooui-dialog-process-retry": "Қайта байқап көріңіз",
+ "ooui-dialog-process-continue": "Жалғастыру"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/km.json b/vendor/oojs/oojs-ui/i18n/km.json
new file mode 100644
index 00000000..c0d72c4f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/km.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sovichet"
+ ]
+ },
+ "ooui-outline-control-move-down": "រុញ​ទៅ​ក្រោម",
+ "ooui-outline-control-move-up": "រុញ​ទៅ​លើ",
+ "ooui-outline-control-remove": "ដក​វត្ថុ​ចេញ",
+ "ooui-toolbar-more": "បន្ថែម"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/kn.json b/vendor/oojs/oojs-ui/i18n/kn.json
new file mode 100644
index 00000000..982a3cdf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/kn.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Vikassy",
+ "Nayvik",
+ "Omshivaprakash",
+ "Pavanaja"
+ ]
+ },
+ "ooui-outline-control-move-down": "ವಸ್ತುವನ್ನು ಕೆಳಗೆ ಸರಿಸು",
+ "ooui-outline-control-move-up": "ವಸ್ತುವನ್ನು ಮೇಲೆ ಸರಿಸು",
+ "ooui-outline-control-remove": "ವಸ್ತುವನ್ನು ತೆಗೆ",
+ "ooui-toolbar-more": "ಇನ್ನಷ್ಟು",
+ "ooui-toolgroup-expand": "ಇನ್ನಷ್ಟು",
+ "ooui-toolgroup-collapse": "ಕೆಲವೇ ಕೆಲವು",
+ "ooui-dialog-message-accept": "ಸರಿ",
+ "ooui-dialog-message-reject": "ರದ್ದುಮಾಡು",
+ "ooui-dialog-process-error": "ಏನೋ ಎಡವಟ್ಟಾಗಿದೆ....",
+ "ooui-dialog-process-dismiss": "ತೆಗೆದುಹಾಕು",
+ "ooui-dialog-process-retry": "ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ",
+ "ooui-dialog-process-continue": "ಮುಂದುವರೆಸು"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ko.json b/vendor/oojs/oojs-ui/i18n/ko.json
new file mode 100644
index 00000000..196dc2c3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ko.json
@@ -0,0 +1,25 @@
+{
+ "@metadata": {
+ "authors": [
+ "Freebiekr",
+ "Hym411",
+ "Kwj2772",
+ "LFM",
+ "아라",
+ "고기랑",
+ "Ryuch",
+ "Revi",
+ "Infinity"
+ ]
+ },
+ "ooui-outline-control-move-down": "항목을 아래로 옮기기",
+ "ooui-outline-control-move-up": "항목을 위로 옮기기",
+ "ooui-outline-control-remove": "항목 지우기",
+ "ooui-toolbar-more": "더 보기",
+ "ooui-toolgroup-expand": "더 보기",
+ "ooui-dialog-message-accept": "확인",
+ "ooui-dialog-message-reject": "취소",
+ "ooui-dialog-process-error": "무언가가 잘못되었습니다",
+ "ooui-dialog-process-dismiss": "숨기기",
+ "ooui-dialog-process-retry": "다시 시도하세요"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/krc.json b/vendor/oojs/oojs-ui/i18n/krc.json
new file mode 100644
index 00000000..ef92e49f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/krc.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Iltever"
+ ]
+ },
+ "ooui-outline-control-move-down": "Элементни тюбюне кёчюр",
+ "ooui-outline-control-move-up": "Элементни башына кёчюр",
+ "ooui-outline-control-remove": "Пунктну кетер",
+ "ooui-toolbar-more": "Энтда",
+ "ooui-toolgroup-expand": "Энтда",
+ "ooui-toolgroup-collapse": "Артха",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Ызына ал",
+ "ooui-dialog-process-error": "Не эсе да табсыз кетди",
+ "ooui-dialog-process-dismiss": "Джаб",
+ "ooui-dialog-process-retry": "Энтда сынаб кёр",
+ "ooui-dialog-process-continue": "Бардыр"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ksh.json b/vendor/oojs/oojs-ui/i18n/ksh.json
new file mode 100644
index 00000000..c975e825
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ksh.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Purodha"
+ ]
+ },
+ "ooui-outline-control-move-down": "Öm eine Plaz noh onge schiehbe",
+ "ooui-outline-control-move-up": "Öm eine Plaz noh bovve schiehbe",
+ "ooui-outline-control-remove": "Dä Plaz läddesch maache → fott domet!",
+ "ooui-toolbar-more": "Mih",
+ "ooui-toolgroup-expand": "Mih",
+ "ooui-toolgroup-collapse": "Winnijer",
+ "ooui-dialog-message-accept": "Lohß Jonn!",
+ "ooui-dialog-message-reject": "Ophühre",
+ "ooui-dialog-process-error": "Öhnsjädd es scheif jejange",
+ "ooui-dialog-process-dismiss": "Maach fott, ha_sch jelässe",
+ "ooui-dialog-process-retry": "Norr_ens versöhke",
+ "ooui-dialog-process-continue": "Wigger maache"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ku-latn.json b/vendor/oojs/oojs-ui/i18n/ku-latn.json
new file mode 100644
index 00000000..be9a8abd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ku-latn.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "George Animal"
+ ]
+ },
+ "ooui-toolgroup-expand": "Bêhtir",
+ "ooui-toolgroup-collapse": "Kêmtir",
+ "ooui-dialog-message-accept": "Baş e",
+ "ooui-dialog-message-reject": "Betal bike",
+ "ooui-dialog-process-retry": "Dîsa hewl bide",
+ "ooui-dialog-process-continue": "Bidomîne"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/kw.json b/vendor/oojs/oojs-ui/i18n/kw.json
new file mode 100644
index 00000000..a6c6d8ab
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/kw.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "George Animal",
+ "Nrowe",
+ "Purodha"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ky.json b/vendor/oojs/oojs-ui/i18n/ky.json
new file mode 100644
index 00000000..e2b8ab7a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ky.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chorobek",
+ "George Animal",
+ "Nrowe",
+ "Tynchtyk Chorotegin",
+ "Викиней"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/lb.json b/vendor/oojs/oojs-ui/i18n/lb.json
new file mode 100644
index 00000000..119d1be9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/lb.json
@@ -0,0 +1,25 @@
+{
+ "@metadata": {
+ "authors": [
+ "Autokrator",
+ "Chorobek",
+ "Robby",
+ "Soued031",
+ "Tynchtyk Chorotegin",
+ "UV",
+ "Викиней"
+ ]
+ },
+ "ooui-outline-control-move-down": "Element erof réckelen",
+ "ooui-outline-control-move-up": "Element erop réckelen",
+ "ooui-outline-control-remove": "Element ewechhuelen",
+ "ooui-toolbar-more": "Méi",
+ "ooui-toolgroup-expand": "Méi",
+ "ooui-toolgroup-collapse": "Manner",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Ofbriechen",
+ "ooui-dialog-process-error": "Et ass eppes schif gaang",
+ "ooui-dialog-process-dismiss": "Verwerfen",
+ "ooui-dialog-process-retry": "Nach eng Kéier probéieren",
+ "ooui-dialog-process-continue": "Virufueren"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/lmo.json b/vendor/oojs/oojs-ui/i18n/lmo.json
new file mode 100644
index 00000000..87309db0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/lmo.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ninonino"
+ ]
+ },
+ "ooui-outline-control-move-down": "Spòsta 'n zó",
+ "ooui-outline-control-move-up": "Spòsta 'n sö",
+ "ooui-toolbar-more": "Amò"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/lt.json b/vendor/oojs/oojs-ui/i18n/lt.json
new file mode 100644
index 00000000..ecd06a8a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/lt.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Audriusa",
+ "Eitvys200",
+ "Mantak111"
+ ]
+ },
+ "ooui-outline-control-remove": "Šalinti elementus"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/lv.json b/vendor/oojs/oojs-ui/i18n/lv.json
new file mode 100644
index 00000000..9ff787ac
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/lv.json
@@ -0,0 +1,21 @@
+{
+ "@metadata": {
+ "authors": [
+ "Admresdeserv.",
+ "Audriusa",
+ "Eitvys200",
+ "Papuass",
+ "PeterisP"
+ ]
+ },
+ "ooui-outline-control-move-down": "Pārvietot vienumu uz leju",
+ "ooui-outline-control-move-up": "Pārvietot vienumu uz augšu",
+ "ooui-toolbar-more": "Vairāk",
+ "ooui-toolgroup-expand": "Vairāk",
+ "ooui-toolgroup-collapse": "Mazāk",
+ "ooui-dialog-message-accept": "Labi",
+ "ooui-dialog-message-reject": "Atcelt",
+ "ooui-dialog-process-error": "Kaut kas nogāja greizi",
+ "ooui-dialog-process-retry": "Mēģināt vēlreiz",
+ "ooui-dialog-process-continue": "Turpināt"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/lzh.json b/vendor/oojs/oojs-ui/i18n/lzh.json
new file mode 100644
index 00000000..2b3ad53c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/lzh.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Joe young yu"
+ ]
+ },
+ "ooui-dialog-message-accept": "可"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/mg.json b/vendor/oojs/oojs-ui/i18n/mg.json
new file mode 100644
index 00000000..af97d171
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/mg.json
@@ -0,0 +1,7 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jagwar"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/min.json b/vendor/oojs/oojs-ui/i18n/min.json
new file mode 100644
index 00000000..b8790d31
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/min.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "Iwan Novirion",
+ "Jagwar"
+ ]
+ },
+ "ooui-outline-control-move-down": "Pindahan ko ka bawah",
+ "ooui-outline-control-move-up": "Pindahan ko ka ateh",
+ "ooui-outline-control-remove": "Hapuih ko",
+ "ooui-toolbar-more": "Lainnyo",
+ "ooui-dialog-message-accept": "Yo",
+ "ooui-dialog-message-reject": "Batal"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/mk.json b/vendor/oojs/oojs-ui/i18n/mk.json
new file mode 100644
index 00000000..7962336c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/mk.json
@@ -0,0 +1,21 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bjankuloski06",
+ "Brest",
+ "Iwan Novirion"
+ ]
+ },
+ "ooui-outline-control-move-down": "Помести надолу",
+ "ooui-outline-control-move-up": "Помести нагоре",
+ "ooui-outline-control-remove": "Отстрани ставка",
+ "ooui-toolbar-more": "Повеќе",
+ "ooui-toolgroup-expand": "Повеќе",
+ "ooui-toolgroup-collapse": "Помалку",
+ "ooui-dialog-message-accept": "ОК",
+ "ooui-dialog-message-reject": "Откажи",
+ "ooui-dialog-process-error": "Нешто не е во ред",
+ "ooui-dialog-process-dismiss": "Тргни",
+ "ooui-dialog-process-retry": "Обиди се пак",
+ "ooui-dialog-process-continue": "Продолжи"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ml.json b/vendor/oojs/oojs-ui/i18n/ml.json
new file mode 100644
index 00000000..0ce0c3fd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ml.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kavya Manohar",
+ "Praveenp",
+ "Santhosh.thottingal",
+ "Vssun"
+ ]
+ },
+ "ooui-outline-control-move-down": "ഇനം താഴേയ്ക്ക് മാറ്റുക",
+ "ooui-outline-control-move-up": "ഇനം മുകളിലേയ്ക്ക് മാറ്റുക",
+ "ooui-toolbar-more": "കൂടുതൽ"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/mr.json b/vendor/oojs/oojs-ui/i18n/mr.json
new file mode 100644
index 00000000..70061907
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/mr.json
@@ -0,0 +1,15 @@
+{
+ "@metadata": {
+ "authors": [
+ "Kaajawa",
+ "Mahitgar",
+ "Praju23",
+ "V.narsikar",
+ "Ydyashad",
+ "संतोष दहिवळ"
+ ]
+ },
+ "ooui-outline-control-move-down": "घटक (आयटम) खाली सरकवा",
+ "ooui-outline-control-move-up": "घटक (आयटम) वर सरकवा",
+ "ooui-toolbar-more": "अधिक"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ms.json b/vendor/oojs/oojs-ui/i18n/ms.json
new file mode 100644
index 00000000..823d4936
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ms.json
@@ -0,0 +1,17 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anakmalaysia",
+ "Aurora",
+ "Pizza1016"
+ ]
+ },
+ "ooui-outline-control-move-down": "Alihkan perkara ke bawah",
+ "ooui-outline-control-move-up": "Alihkan perkara ke atas",
+ "ooui-outline-control-remove": "Buang perkara",
+ "ooui-toolbar-more": "Selebihnya",
+ "ooui-dialog-message-reject": "Batal",
+ "ooui-dialog-process-error": "Ada masalah",
+ "ooui-dialog-process-dismiss": "Singkir",
+ "ooui-dialog-process-retry": "Cuba lagi"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/nap.json b/vendor/oojs/oojs-ui/i18n/nap.json
new file mode 100644
index 00000000..91660373
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/nap.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Chelin",
+ "Chrisportelli",
+ "PiRSquared17"
+ ]
+ },
+ "ooui-toolbar-more": "Atro"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/nb.json b/vendor/oojs/oojs-ui/i18n/nb.json
new file mode 100644
index 00000000..9e773924
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/nb.json
@@ -0,0 +1,25 @@
+{
+ "@metadata": {
+ "authors": [
+ "Danmichaelo",
+ "Event",
+ "Jeblad",
+ "Laaknor",
+ "Njardarlogar",
+ "Jdforrester",
+ "Apple farmer"
+ ]
+ },
+ "ooui-outline-control-move-down": "Flytt ned",
+ "ooui-outline-control-move-up": "Flytt opp",
+ "ooui-outline-control-remove": "Fjern element",
+ "ooui-toolbar-more": "Mer",
+ "ooui-toolgroup-expand": "Mer",
+ "ooui-toolgroup-collapse": "Færre",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Avbryt",
+ "ooui-dialog-process-error": "Noe gikk galt",
+ "ooui-dialog-process-dismiss": "Lukk",
+ "ooui-dialog-process-retry": "Prøv igjen",
+ "ooui-dialog-process-continue": "Fortsett"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/nds-nl.json b/vendor/oojs/oojs-ui/i18n/nds-nl.json
new file mode 100644
index 00000000..d3db318b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/nds-nl.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Servien"
+ ]
+ },
+ "ooui-outline-control-move-down": "Onderwarp ummeneer zetten",
+ "ooui-outline-control-move-up": "Onderwarp umhoge zetten",
+ "ooui-outline-control-remove": "Element vortdoon",
+ "ooui-toolbar-more": "Meer",
+ "ooui-toolgroup-expand": "Meer",
+ "ooui-toolgroup-collapse": "Minder",
+ "ooui-dialog-message-accept": "Okee",
+ "ooui-dialog-message-reject": "Aofbreken",
+ "ooui-dialog-process-error": "Der gung iets fout",
+ "ooui-dialog-process-dismiss": "Sluten",
+ "ooui-dialog-process-retry": "Opniej proberen",
+ "ooui-dialog-process-continue": "Deurgaon"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/nds.json b/vendor/oojs/oojs-ui/i18n/nds.json
new file mode 100644
index 00000000..1e5b83dd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/nds.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Zylbath"
+ ]
+ },
+ "ooui-outline-control-move-down": "Element na ünnen schuven",
+ "ooui-outline-control-move-up": "Element na baven schuven",
+ "ooui-toolbar-more": "Mehr"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ne.json b/vendor/oojs/oojs-ui/i18n/ne.json
new file mode 100644
index 00000000..4ca5ab72
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ne.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "RajeshPandey",
+ "सरोज कुमार ढकाल",
+ "Ganesh Paudel"
+ ]
+ },
+ "ooui-outline-control-move-down": "वस्तुलाई तल सार्ने",
+ "ooui-outline-control-move-up": "वस्तुलाई माथि सार्ने",
+ "ooui-outline-control-remove": "वस्तुलाई हटाउने",
+ "ooui-toolbar-more": "थप",
+ "ooui-toolgroup-expand": "थप",
+ "ooui-toolgroup-collapse": "कम",
+ "ooui-dialog-message-accept": "हुन्छ",
+ "ooui-dialog-message-reject": "रद्द गर्ने",
+ "ooui-dialog-process-dismiss": "खारेज गर्ने",
+ "ooui-dialog-process-retry": "पुन प्रयास गर्नुहोस",
+ "ooui-dialog-process-continue": "जारी राख्ने"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/nl.json b/vendor/oojs/oojs-ui/i18n/nl.json
new file mode 100644
index 00000000..7c7b1767
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/nl.json
@@ -0,0 +1,35 @@
+{
+ "@metadata": {
+ "authors": [
+ "Bluyten",
+ "Breghtje",
+ "Catrope",
+ "Flightmare",
+ "Hansmuller",
+ "Jdforrester",
+ "Keegan",
+ "Konovalov",
+ "RajeshPandey",
+ "Romaine",
+ "SPQRobin",
+ "Saruman",
+ "Siebrand",
+ "Southparkfan",
+ "सरोज कुमार ढकाल",
+ "Sjoerddebruin",
+ "Gloria sah"
+ ]
+ },
+ "ooui-outline-control-move-down": "Item omlaag verplaatsen",
+ "ooui-outline-control-move-up": "Item omhoog verplaatsen",
+ "ooui-outline-control-remove": "Item verwijderen",
+ "ooui-toolbar-more": "Meer",
+ "ooui-toolgroup-expand": "Meer",
+ "ooui-toolgroup-collapse": "Minder",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Annuleren",
+ "ooui-dialog-process-error": "Er is iets misgegaan",
+ "ooui-dialog-process-dismiss": "Sluiten",
+ "ooui-dialog-process-retry": "Opnieuw proberen",
+ "ooui-dialog-process-continue": "Doorgaan"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/nn.json b/vendor/oojs/oojs-ui/i18n/nn.json
new file mode 100644
index 00000000..943e6adc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/nn.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jeblad",
+ "Njardarlogar"
+ ]
+ },
+ "ooui-outline-control-move-down": "Flytt element ned",
+ "ooui-outline-control-move-up": "Flytt element opp",
+ "ooui-toolbar-more": "Fleire"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/oc.json b/vendor/oojs/oojs-ui/i18n/oc.json
new file mode 100644
index 00000000..4d35b6c9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/oc.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cedric31",
+ "Gloria sah"
+ ]
+ },
+ "ooui-outline-control-move-down": "Far davalar l’element",
+ "ooui-outline-control-move-up": "Far montar l’element",
+ "ooui-outline-control-remove": "Suprimir l’element",
+ "ooui-toolbar-more": "Mai",
+ "ooui-dialog-message-reject": "Anullar"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/om.json b/vendor/oojs/oojs-ui/i18n/om.json
new file mode 100644
index 00000000..ecf95971
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/om.json
@@ -0,0 +1,20 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cedric31",
+ "Tumsaa"
+ ]
+ },
+ "ooui-outline-control-move-down": "Gad buusi",
+ "ooui-outline-control-move-up": "Ol baasi",
+ "ooui-outline-control-remove": "Balleessi",
+ "ooui-toolbar-more": "Dabalata",
+ "ooui-toolgroup-expand": "Dabalata",
+ "ooui-toolgroup-collapse": "Xiqqaa",
+ "ooui-dialog-message-accept": "Tole",
+ "ooui-dialog-message-reject": "Dhiisi",
+ "ooui-dialog-process-error": "Dogoggorri wayii ummameera",
+ "ooui-dialog-process-dismiss": "Didi",
+ "ooui-dialog-process-retry": "Itti deebi'ii yaali",
+ "ooui-dialog-process-continue": "Itti fufi"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/or.json b/vendor/oojs/oojs-ui/i18n/or.json
new file mode 100644
index 00000000..dde49bf1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/or.json
@@ -0,0 +1,21 @@
+{
+ "@metadata": {
+ "authors": [
+ "Odisha1",
+ "Psubhashish",
+ "ଶିତିକଣ୍ଠ ଦାଶ"
+ ]
+ },
+ "ooui-outline-control-move-down": "ବସ୍ତୁଟିକୁ ତଳକୁ ଘୁଞ୍ଚାନ୍ତୁ",
+ "ooui-outline-control-move-up": "ବସ୍ତୁଟିକୁ ଉପରକୁ ଘୁଞ୍ଚାନ୍ତୁ",
+ "ooui-outline-control-remove": "ବସ୍ତୁଟିକୁ ଲିଭାନ୍ତୁ",
+ "ooui-toolbar-more": "ଅଧିକ",
+ "ooui-toolgroup-expand": "ଅଧିକ",
+ "ooui-toolgroup-collapse": "ଅଳ୍ପ",
+ "ooui-dialog-message-accept": "ହେଉ",
+ "ooui-dialog-message-reject": "ନାକଚ",
+ "ooui-dialog-process-error": "ଅସୁବିଧାଟିଏ ଘଟିଲା",
+ "ooui-dialog-process-dismiss": "ଖାରଜ",
+ "ooui-dialog-process-retry": "ଆଉ ଥରେ ଚେଷ୍ଟା କରନ୍ତୁ",
+ "ooui-dialog-process-continue": "ଚାଲୁରଖିବେ"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/pa.json b/vendor/oojs/oojs-ui/i18n/pa.json
new file mode 100644
index 00000000..8c7a1e7c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/pa.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amikeco",
+ "Babanwalia",
+ "Bouron",
+ "Nasir8891",
+ "Satdeep gill"
+ ]
+ },
+ "ooui-outline-control-move-down": "ਨੀਚੇ ਲੈਕੇ ਜਾਓ",
+ "ooui-outline-control-move-up": "ਉੱਤੇ ਲੈਕੇ ਜਾਓ",
+ "ooui-toolbar-more": "ਹੋਰ",
+ "ooui-toolgroup-expand": "ਹੋਰ",
+ "ooui-toolgroup-collapse": "ਥੋੜੇ",
+ "ooui-dialog-message-accept": "ਠੀਕ ਹੈ",
+ "ooui-dialog-message-reject": "ਰੱਦ ਕਰੋ",
+ "ooui-dialog-process-error": "ਕੁਝ ਗਲਤ ਹੋ ਗਿਆ",
+ "ooui-dialog-process-dismiss": "ਰੱਦ ਕਰੋ",
+ "ooui-dialog-process-retry": "ਦੁਬਾਰਾ ਕੋਸ਼ਿਸ਼ ਕਰੋ",
+ "ooui-dialog-process-continue": "ਜਾਰੀ ਰੱਖੋ"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/pfl.json b/vendor/oojs/oojs-ui/i18n/pfl.json
new file mode 100644
index 00000000..02d08426
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/pfl.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Manuae"
+ ]
+ },
+ "ooui-outline-control-move-down": "Bweeschs nunna",
+ "ooui-outline-control-move-up": "Bweeschs nuff",
+ "ooui-outline-control-remove": "Leschs",
+ "ooui-toolbar-more": "Mea",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Abbresche"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/pl.json b/vendor/oojs/oojs-ui/i18n/pl.json
new file mode 100644
index 00000000..fbd0c8cc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/pl.json
@@ -0,0 +1,34 @@
+{
+ "@metadata": {
+ "authors": [
+ "Babanwalia",
+ "Chrumps",
+ "Matma Rex",
+ "Mikołka",
+ "Nasir8891",
+ "Odie2",
+ "Rzuwig",
+ "Tar Lócesilion",
+ "Ty221",
+ "WTM",
+ "Woytecr",
+ "Wpedzich",
+ "Jacenty359",
+ "Matik7",
+ "Gloria sah",
+ "Andrzej aa"
+ ]
+ },
+ "ooui-outline-control-move-down": "Przenieś niżej",
+ "ooui-outline-control-move-up": "Przenieś wyżej",
+ "ooui-outline-control-remove": "Usuń element",
+ "ooui-toolbar-more": "Więcej",
+ "ooui-toolgroup-expand": "Więcej",
+ "ooui-toolgroup-collapse": "Mniej",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Anuluj",
+ "ooui-dialog-process-error": "Coś poszło nie tak",
+ "ooui-dialog-process-dismiss": "Ukryj",
+ "ooui-dialog-process-retry": "Spróbuj ponownie",
+ "ooui-dialog-process-continue": "Kontynuuj"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/pms.json b/vendor/oojs/oojs-ui/i18n/pms.json
new file mode 100644
index 00000000..b8fd3a58
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/pms.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Borichèt",
+ "Dragonòt",
+ "පසිඳු කාවින්ද"
+ ]
+ },
+ "ooui-outline-control-move-down": "Fé calé giù l'element",
+ "ooui-outline-control-move-up": "Fé monté l'element",
+ "ooui-toolbar-more": "Ëd pi"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ps.json b/vendor/oojs/oojs-ui/i18n/ps.json
new file mode 100644
index 00000000..ebffe539
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ps.json
@@ -0,0 +1,18 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ahmed-Najib-Biabani-Ibrahimkhel"
+ ]
+ },
+ "ooui-outline-control-move-down": "توکی ښکته راوړل",
+ "ooui-outline-control-move-up": "توکی پورته راوړل",
+ "ooui-outline-control-remove": "توکی غورځول",
+ "ooui-toolbar-more": "نور",
+ "ooui-toolgroup-expand": "نور",
+ "ooui-toolgroup-collapse": "لږ تر لږ",
+ "ooui-dialog-message-accept": "ښه",
+ "ooui-dialog-message-reject": "ناگارل",
+ "ooui-dialog-process-error": "يوه ستونزه رامنځ ته شوه",
+ "ooui-dialog-process-dismiss": "تړل",
+ "ooui-dialog-process-retry": "بيا هڅه"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/pt-br.json b/vendor/oojs/oojs-ui/i18n/pt-br.json
new file mode 100644
index 00000000..94ea0895
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/pt-br.json
@@ -0,0 +1,18 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cainamarques",
+ "Dianakc",
+ "Fúlvio",
+ "Helder.wiki",
+ "HenriqueCrang",
+ "Jaideraf",
+ "Luckas",
+ "OTAVIO1981",
+ 555
+ ]
+ },
+ "ooui-outline-control-move-down": "Mover item para baixo",
+ "ooui-outline-control-move-up": "Mover item para cima",
+ "ooui-toolbar-more": "Mais"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/pt.json b/vendor/oojs/oojs-ui/i18n/pt.json
new file mode 100644
index 00000000..7b3176fb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/pt.json
@@ -0,0 +1,28 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cainamarques",
+ "Fúlvio",
+ "GoEThe",
+ "Hamilton Abreu",
+ "Helder.wiki",
+ "Jaideraf",
+ "Jdforrester",
+ "Luckas",
+ "Vitorvicentevalente",
+ "SandroHc"
+ ]
+ },
+ "ooui-outline-control-move-down": "Mover item para baixo",
+ "ooui-outline-control-move-up": "Mover item para cima",
+ "ooui-outline-control-remove": "Remover elemento",
+ "ooui-toolbar-more": "Mais",
+ "ooui-toolgroup-expand": "Mais",
+ "ooui-toolgroup-collapse": "Menos",
+ "ooui-dialog-message-accept": "Aceitar",
+ "ooui-dialog-message-reject": "Cancelar",
+ "ooui-dialog-process-error": "Algo correu mal",
+ "ooui-dialog-process-dismiss": "Ignorar",
+ "ooui-dialog-process-retry": "Tentar novamente",
+ "ooui-dialog-process-continue": "Continuar"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/qqq.json b/vendor/oojs/oojs-ui/i18n/qqq.json
new file mode 100644
index 00000000..c1b794ab
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/qqq.json
@@ -0,0 +1,35 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amire80",
+ "Beta16",
+ "Erik Moeller",
+ "Jdforrester",
+ "Lloffiwr",
+ "Mooeypoo",
+ "Mormegil",
+ "Nike",
+ "PoLuX124",
+ "Purodha",
+ "Raymond",
+ "Sagan",
+ "Sayak Sarkar",
+ "Shirayuki",
+ "Siebrand",
+ "Trevor Parscal",
+ "Liuxinyu970226"
+ ]
+ },
+ "ooui-outline-control-move-down": "Tool tip for a button that moves items in a list down one place",
+ "ooui-outline-control-move-up": "Tool tip for a button that moves items in a list up one place",
+ "ooui-outline-control-remove": "Tool tip for a button that removes items from a list.\n{{Identical|Remove item}}",
+ "ooui-toolbar-more": "Label for the toolbar group that contains a list of all other available tools.\n{{Identical|More}}",
+ "ooui-toolgroup-expand": "Label for the fake tool that expands the full list of tools in a toolbar group.\n\nSee also:\n* {{msg-mw|Ooui-toolgroup-collapse}}\n{{Identical|More}}",
+ "ooui-toolgroup-collapse": "Label for the fake tool that collapses the full list of tools in a toolbar group.\n\nSee also:\n* {{msg-mw|Ooui-toolgroup-expand}}\n{{Identical|Fewer}}",
+ "ooui-dialog-message-accept": "Default label for the accept button of a message dialog\n{{Identical|OK}}",
+ "ooui-dialog-message-reject": "Default label for the reject button of a message dialog\n{{Identical|Cancel}}",
+ "ooui-dialog-process-error": "Title for process dialog error description",
+ "ooui-dialog-process-dismiss": "Label for process dialog dismiss error button, visible when describing errors\n{{Identical|Dismiss}}",
+ "ooui-dialog-process-retry": "Label for process dialog retry action button, visible when describing recoverable errors\n{{Identical|Try again}}",
+ "ooui-dialog-process-continue": "Label for process dialog retry action button, visible when describing only warnings\n{{Identical|Continue}}"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/qu.json b/vendor/oojs/oojs-ui/i18n/qu.json
new file mode 100644
index 00000000..cb0b2c33
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/qu.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "AlimanRuna",
+ "Jduranboger"
+ ]
+ },
+ "ooui-outline-control-move-down": "Qallawata uraykuchiy",
+ "ooui-outline-control-move-up": "Qallawata huqariy",
+ "ooui-outline-control-remove": "P'anqa sutikunata qichuy",
+ "ooui-toolbar-more": "Aswan"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ro.json b/vendor/oojs/oojs-ui/i18n/ro.json
new file mode 100644
index 00000000..258f3e74
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ro.json
@@ -0,0 +1,23 @@
+{
+ "@metadata": {
+ "authors": [
+ "AlimanRuna",
+ "Firilacroco",
+ "Minisarm",
+ "Stelistcristi",
+ "Gloria sah"
+ ]
+ },
+ "ooui-outline-control-move-down": "Mută elementul mai jos",
+ "ooui-outline-control-move-up": "Mută elementul mai sus",
+ "ooui-outline-control-remove": "Elimină elementul",
+ "ooui-toolbar-more": "Mai mult",
+ "ooui-toolgroup-expand": "Mai multe",
+ "ooui-toolgroup-collapse": "Mai puține",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Revocare",
+ "ooui-dialog-process-error": "Ceva nu a funcționat",
+ "ooui-dialog-process-dismiss": "Renunțare",
+ "ooui-dialog-process-retry": "Reîncearcă",
+ "ooui-dialog-process-continue": "Continuă"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/roa-tara.json b/vendor/oojs/oojs-ui/i18n/roa-tara.json
new file mode 100644
index 00000000..f6f422a2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/roa-tara.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Joetaras"
+ ]
+ },
+ "ooui-outline-control-move-down": "Spuèste 'a vôsce sotte",
+ "ooui-outline-control-move-up": "Spuèste 'a vôsce sus",
+ "ooui-outline-control-remove": "Live 'a vôsce",
+ "ooui-toolbar-more": "De cchiù",
+ "ooui-toolgroup-expand": "De cchiù",
+ "ooui-toolgroup-collapse": "De mene",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Annulle",
+ "ooui-dialog-process-error": "Quacche cose ha sciute stuèrte",
+ "ooui-dialog-process-dismiss": "Scitte",
+ "ooui-dialog-process-retry": "Pruève arrete",
+ "ooui-dialog-process-continue": "Condinue"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ru.json b/vendor/oojs/oojs-ui/i18n/ru.json
new file mode 100644
index 00000000..129dd6a2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ru.json
@@ -0,0 +1,34 @@
+{
+ "@metadata": {
+ "authors": [
+ "Amire80",
+ "DR",
+ "Eugrus",
+ "Iluvatar",
+ "KPu3uC B Poccuu",
+ "Kalan",
+ "MaxBioHazard",
+ "NBS",
+ "Niklem",
+ "Okras",
+ "Ole Yves",
+ "Putnik",
+ "Sunpriat",
+ "Yury Katkov",
+ "Умар",
+ "Камалист"
+ ]
+ },
+ "ooui-outline-control-move-down": "Переместить элемент вниз",
+ "ooui-outline-control-move-up": "Переместить элемент вверх",
+ "ooui-outline-control-remove": "Удалить пункт",
+ "ooui-toolbar-more": "Ещё",
+ "ooui-toolgroup-expand": "Больше",
+ "ooui-toolgroup-collapse": "Меньше",
+ "ooui-dialog-message-accept": "ОК",
+ "ooui-dialog-message-reject": "Отмена",
+ "ooui-dialog-process-error": "Что-то пошло не так",
+ "ooui-dialog-process-dismiss": "Закрыть",
+ "ooui-dialog-process-retry": "Попробовать ещё раз",
+ "ooui-dialog-process-continue": "Продолжить"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sah.json b/vendor/oojs/oojs-ui/i18n/sah.json
new file mode 100644
index 00000000..85a94cd5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sah.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gazeb",
+ "HalanTul"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/scn.json b/vendor/oojs/oojs-ui/i18n/scn.json
new file mode 100644
index 00000000..22a212f9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/scn.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Gazeb",
+ "Gmelfi",
+ "HalanTul",
+ "Gloria sah"
+ ]
+ },
+ "ooui-outline-control-move-down": "Sposta di sutta",
+ "ooui-outline-control-move-up": "Sposta di supra",
+ "ooui-toolbar-more": "Àutri cosi"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sco.json b/vendor/oojs/oojs-ui/i18n/sco.json
new file mode 100644
index 00000000..0a26a5c8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sco.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "John Reid"
+ ]
+ },
+ "ooui-outline-control-move-down": "Muiv eetem doon",
+ "ooui-outline-control-move-up": "Muiv eetem up",
+ "ooui-outline-control-remove": "Remuiv eetem",
+ "ooui-toolbar-more": "Mair"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sh.json b/vendor/oojs/oojs-ui/i18n/sh.json
new file mode 100644
index 00000000..b40fa04e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sh.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "OC Ripper"
+ ]
+ },
+ "ooui-outline-control-move-down": "Pomakni stavku dolje",
+ "ooui-outline-control-move-up": "Pomakni stavku gore"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/si.json b/vendor/oojs/oojs-ui/i18n/si.json
new file mode 100644
index 00000000..5988773b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/si.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Singhalawap",
+ "පසිඳු කාවින්ද",
+ "ශ්වෙත"
+ ]
+ },
+ "ooui-outline-control-move-down": "අයිතමය පහලටදමන්න",
+ "ooui-outline-control-move-up": "අයිතමය ඉහලටදමන්න"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sk.json b/vendor/oojs/oojs-ui/i18n/sk.json
new file mode 100644
index 00000000..c8246da9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sk.json
@@ -0,0 +1,12 @@
+{
+ "@metadata": {
+ "authors": [
+ "Mimarik",
+ "Teslaton"
+ ]
+ },
+ "ooui-outline-control-move-down": "Posunúť položku nadol",
+ "ooui-outline-control-move-up": "Posunúť položku nahor",
+ "ooui-outline-control-remove": "Odstrániť položku",
+ "ooui-toolbar-more": "Viac"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sl.json b/vendor/oojs/oojs-ui/i18n/sl.json
new file mode 100644
index 00000000..a40728ad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sl.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Dbc334",
+ "Eleassar",
+ "Pinky sl",
+ "Yerpo"
+ ]
+ },
+ "ooui-outline-control-move-down": "Prestavi predmet nižje",
+ "ooui-outline-control-move-up": "Prestavi predmet višje",
+ "ooui-outline-control-remove": "Odstrani vnos",
+ "ooui-toolbar-more": "Več",
+ "ooui-toolgroup-expand": "Več",
+ "ooui-toolgroup-collapse": "Manj",
+ "ooui-dialog-message-accept": "V redu",
+ "ooui-dialog-message-reject": "Prekliči",
+ "ooui-dialog-process-error": "Nekaj je šlo narobe",
+ "ooui-dialog-process-dismiss": "Skrij",
+ "ooui-dialog-process-retry": "Poskusi znova",
+ "ooui-dialog-process-continue": "Nadaljuj"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sq.json b/vendor/oojs/oojs-ui/i18n/sq.json
new file mode 100644
index 00000000..ec180199
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sq.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Euriditi",
+ "Kushtrim",
+ "Elioqoshi",
+ "GretaDoci",
+ "Gertakapllani"
+ ]
+ },
+ "ooui-outline-control-move-down": "Zhvendose artikullin më poshtë",
+ "ooui-outline-control-move-up": "Zhvendose artikullin më lart",
+ "ooui-outline-control-remove": "Hiq artikullin",
+ "ooui-toolbar-more": "Më tepër...",
+ "ooui-dialog-message-accept": "Në rregull",
+ "ooui-dialog-message-reject": "Anullo",
+ "ooui-dialog-process-error": "Diçka shkoi keq",
+ "ooui-dialog-process-retry": "Provo përsëri"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sr-ec.json b/vendor/oojs/oojs-ui/i18n/sr-ec.json
new file mode 100644
index 00000000..c827554e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sr-ec.json
@@ -0,0 +1,21 @@
+{
+ "@metadata": {
+ "authors": [
+ "Milicevic01",
+ "Nikola Smolenski",
+ "Милан Јелисавчић"
+ ]
+ },
+ "ooui-outline-control-move-down": "Премести ставку на доле",
+ "ooui-outline-control-move-up": "Премести ставку на горе",
+ "ooui-outline-control-remove": "Уклони ставку",
+ "ooui-toolbar-more": "Више",
+ "ooui-toolgroup-expand": "Више",
+ "ooui-toolgroup-collapse": "Мање",
+ "ooui-dialog-message-accept": "У реду",
+ "ooui-dialog-message-reject": "Откажи",
+ "ooui-dialog-process-error": "Нешто је пошло наопако",
+ "ooui-dialog-process-dismiss": "Одбаци",
+ "ooui-dialog-process-retry": "Покушај поново",
+ "ooui-dialog-process-continue": "Настави"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sr-el.json b/vendor/oojs/oojs-ui/i18n/sr-el.json
new file mode 100644
index 00000000..704a1860
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sr-el.json
@@ -0,0 +1,19 @@
+{
+ "@metadata": {
+ "authors": [
+ "Milicevic01"
+ ]
+ },
+ "ooui-outline-control-move-down": "Premesti stavku na dole",
+ "ooui-outline-control-move-up": "Premesti stavku na gore",
+ "ooui-outline-control-remove": "Ukloni stavku",
+ "ooui-toolbar-more": "Više",
+ "ooui-toolgroup-expand": "Više",
+ "ooui-toolgroup-collapse": "Manje",
+ "ooui-dialog-message-accept": "U redu",
+ "ooui-dialog-message-reject": "Otkaži",
+ "ooui-dialog-process-error": "Nešto je pošlo naopako",
+ "ooui-dialog-process-dismiss": "Odbaci",
+ "ooui-dialog-process-retry": "Pokušaj ponovo",
+ "ooui-dialog-process-continue": "Nastavi"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sv.json b/vendor/oojs/oojs-ui/i18n/sv.json
new file mode 100644
index 00000000..d499427c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sv.json
@@ -0,0 +1,29 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ainali",
+ "Haxpett",
+ "Jopparn",
+ "Knuckles",
+ "Magol",
+ "Milicevic01",
+ "Per",
+ "Sendelbach",
+ "Skalman",
+ "WikiPhoenix",
+ "Lokal Profil"
+ ]
+ },
+ "ooui-outline-control-move-down": "Flytta ned objekt",
+ "ooui-outline-control-move-up": "Flytta upp objekt",
+ "ooui-outline-control-remove": "Ta bort objekt",
+ "ooui-toolbar-more": "Mer",
+ "ooui-toolgroup-expand": "Fler",
+ "ooui-toolgroup-collapse": "Färre",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Avbryt",
+ "ooui-dialog-process-error": "Något gick fel",
+ "ooui-dialog-process-dismiss": "Stäng",
+ "ooui-dialog-process-retry": "Försök igen",
+ "ooui-dialog-process-continue": "Fortsätt"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/sw.json b/vendor/oojs/oojs-ui/i18n/sw.json
new file mode 100644
index 00000000..510e468d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/sw.json
@@ -0,0 +1,15 @@
+{
+ "@metadata": {
+ "authors": [
+ "Lloffiwr",
+ "Muddyb Blast Producer"
+ ]
+ },
+ "ooui-outline-control-move-down": "Sogeza kipengee chini",
+ "ooui-outline-control-move-up": "Sogeza kipengee juu",
+ "ooui-outline-control-remove": "Toa kitu",
+ "ooui-toolbar-more": "Zaidi",
+ "ooui-dialog-message-accept": "Sawa",
+ "ooui-dialog-message-reject": "Batilisha",
+ "ooui-dialog-process-retry": "Jaribu tena"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ta.json b/vendor/oojs/oojs-ui/i18n/ta.json
new file mode 100644
index 00000000..122d4a27
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ta.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Jayarathina",
+ "Sank",
+ "Shanmugamp7",
+ "மதனாஹரன்",
+ "ElangoRamanujam"
+ ]
+ },
+ "ooui-toolgroup-expand": "மேலும்",
+ "ooui-dialog-process-continue": "தொடரவும்"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/te.json b/vendor/oojs/oojs-ui/i18n/te.json
new file mode 100644
index 00000000..d4868706
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/te.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Arjunaraoc",
+ "Jayarathina",
+ "Sank",
+ "Shanmugamp7",
+ "Veeven",
+ "Visdaviva",
+ "மதனாஹரன்"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/tg-cyrl.json b/vendor/oojs/oojs-ui/i18n/tg-cyrl.json
new file mode 100644
index 00000000..1429bedd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/tg-cyrl.json
@@ -0,0 +1,11 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ibrahim"
+ ]
+ },
+ "ooui-outline-control-move-down": "Ҳаракати мавод ба поён",
+ "ooui-outline-control-move-up": "Ҳаракати мавод ба боло",
+ "ooui-outline-control-remove": "Ҳазви мавод",
+ "ooui-toolbar-more": "Бештар"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/th.json b/vendor/oojs/oojs-ui/i18n/th.json
new file mode 100644
index 00000000..94527935
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/th.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Supasate",
+ "Taweetham"
+ ]
+ },
+ "ooui-outline-control-move-down": "เลื่อนรายการลง",
+ "ooui-outline-control-move-up": "ย้ายรายการขึ้น"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/tl.json b/vendor/oojs/oojs-ui/i18n/tl.json
new file mode 100644
index 00000000..b3a9f246
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/tl.json
@@ -0,0 +1,14 @@
+{
+ "@metadata": {
+ "authors": [
+ "AnakngAraw",
+ "Sky Harbor"
+ ]
+ },
+ "ooui-outline-control-move-down": "Ilipat ang aytem pababa",
+ "ooui-outline-control-move-up": "Ilipat ang aytem pataas",
+ "ooui-outline-control-remove": "Tanggalin ang aytem",
+ "ooui-toolbar-more": "Marami pa",
+ "ooui-dialog-message-accept": "Sige",
+ "ooui-dialog-message-reject": "Huwag ituloy"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/tr.json b/vendor/oojs/oojs-ui/i18n/tr.json
new file mode 100644
index 00000000..7b4d4921
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/tr.json
@@ -0,0 +1,27 @@
+{
+ "@metadata": {
+ "authors": [
+ "Emperyan",
+ "Incelemeelemani",
+ "LuCKY",
+ "Maidis",
+ "Rapsar",
+ "Talha Samil Cakir",
+ "TurkishStyles",
+ "Sayginer",
+ "Meelo"
+ ]
+ },
+ "ooui-outline-control-move-down": "Ögeyi aşağı taşı",
+ "ooui-outline-control-move-up": "Ögeyi yukarı taşı",
+ "ooui-outline-control-remove": "Ögeyi kaldır",
+ "ooui-toolbar-more": "Dahası",
+ "ooui-toolgroup-expand": "Dahası",
+ "ooui-toolgroup-collapse": "Daha az",
+ "ooui-dialog-message-accept": "Tamam",
+ "ooui-dialog-message-reject": "İptal",
+ "ooui-dialog-process-error": "Bir şeyler yanlış gitti",
+ "ooui-dialog-process-dismiss": "Kapat",
+ "ooui-dialog-process-retry": "Tekrar dene",
+ "ooui-dialog-process-continue": "Devam et"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/tt-cyrl.json b/vendor/oojs/oojs-ui/i18n/tt-cyrl.json
new file mode 100644
index 00000000..408a1b4a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/tt-cyrl.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Ajdar"
+ ]
+ },
+ "ooui-outline-control-move-down": "Элементны аска күчерү",
+ "ooui-outline-control-move-up": "Элементны өскә күчерү"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/ug-arab.json b/vendor/oojs/oojs-ui/i18n/ug-arab.json
new file mode 100644
index 00000000..19725cc2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/ug-arab.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Sahran",
+ "Tel'et",
+ "Tifinaghes"
+ ]
+ }
+}
diff --git a/vendor/oojs/oojs-ui/i18n/uk.json b/vendor/oojs/oojs-ui/i18n/uk.json
new file mode 100644
index 00000000..0197a4ce
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/uk.json
@@ -0,0 +1,33 @@
+{
+ "@metadata": {
+ "authors": [
+ "AS",
+ "Aced",
+ "Ahonc",
+ "Andriykopanytsia",
+ "Base",
+ "Perohanych",
+ "RLuts",
+ "Sahran",
+ "Sergento",
+ "Steve.rusyn",
+ "SteveR",
+ "Tel'et",
+ "Tifinaghes",
+ "Ата",
+ "Piramidion"
+ ]
+ },
+ "ooui-outline-control-move-down": "Перемістити елемент униз",
+ "ooui-outline-control-move-up": "Перемістити елемент вгору",
+ "ooui-outline-control-remove": "Видалити елемент",
+ "ooui-toolbar-more": "Більше",
+ "ooui-toolgroup-expand": "Більше",
+ "ooui-toolgroup-collapse": "Менше",
+ "ooui-dialog-message-accept": "Готово",
+ "ooui-dialog-message-reject": "Скасувати",
+ "ooui-dialog-process-error": "Щось пішло не так",
+ "ooui-dialog-process-dismiss": "Приховати",
+ "ooui-dialog-process-retry": "Спробуйте ще раз",
+ "ooui-dialog-process-continue": "Продовжити"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/uz.json b/vendor/oojs/oojs-ui/i18n/uz.json
new file mode 100644
index 00000000..7c6263e4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/uz.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "CoderSI",
+ "Noor2020",
+ "Sociologist",
+ "පසිඳු කාවින්ද"
+ ]
+ },
+ "ooui-outline-control-move-down": "Elementni pastga koʻchirish",
+ "ooui-outline-control-move-up": "Elementni yuqoriga koʻchirish",
+ "ooui-toolbar-more": "Yana"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/vec.json b/vendor/oojs/oojs-ui/i18n/vec.json
new file mode 100644
index 00000000..4de584bf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/vec.json
@@ -0,0 +1,13 @@
+{
+ "@metadata": {
+ "authors": [
+ "Candalua",
+ "GatoSelvadego",
+ "Gloria sah"
+ ]
+ },
+ "ooui-outline-control-move-down": "Sposta in baso",
+ "ooui-outline-control-move-up": "Sposta in sima",
+ "ooui-toolbar-more": "Altro",
+ "ooui-dialog-message-accept": "Va ben"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/vi.json b/vendor/oojs/oojs-ui/i18n/vi.json
new file mode 100644
index 00000000..d5c1e364
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/vi.json
@@ -0,0 +1,22 @@
+{
+ "@metadata": {
+ "authors": [
+ "Cheers!",
+ "Jdforrester",
+ "Minh Nguyen",
+ "Max20091"
+ ]
+ },
+ "ooui-outline-control-move-down": "Chuyển mục xuống",
+ "ooui-outline-control-move-up": "Chuyển mục lên",
+ "ooui-outline-control-remove": "Xóa khoản",
+ "ooui-toolbar-more": "Thêm",
+ "ooui-toolgroup-expand": "Mở rộng",
+ "ooui-toolgroup-collapse": "Rút gọn",
+ "ooui-dialog-message-accept": "OK",
+ "ooui-dialog-message-reject": "Hủy bỏ",
+ "ooui-dialog-process-error": "Đã bị trục trặc",
+ "ooui-dialog-process-dismiss": "Bỏ qua",
+ "ooui-dialog-process-retry": "Thử lại",
+ "ooui-dialog-process-continue": "Tiếp tục"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/vo.json b/vendor/oojs/oojs-ui/i18n/vo.json
new file mode 100644
index 00000000..3510ca93
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/vo.json
@@ -0,0 +1,8 @@
+{
+ "@metadata": {
+ "authors": [
+ "Malafaya"
+ ]
+ },
+ "ooui-toolbar-more": "Pluikos"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/wuu.json b/vendor/oojs/oojs-ui/i18n/wuu.json
new file mode 100644
index 00000000..61e3d01e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/wuu.json
@@ -0,0 +1,9 @@
+{
+ "@metadata": {
+ "authors": [
+ "Malafaya",
+ "十弌"
+ ]
+ },
+ "ooui-toolbar-more": "還多"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/yi.json b/vendor/oojs/oojs-ui/i18n/yi.json
new file mode 100644
index 00000000..a850fce2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/yi.json
@@ -0,0 +1,18 @@
+{
+ "@metadata": {
+ "authors": [
+ "Malafaya",
+ "פוילישער",
+ "十弌"
+ ]
+ },
+ "ooui-outline-control-move-down": "רוקן עלעמענט אראפ",
+ "ooui-outline-control-move-up": "רוקן עלעמענט ארויף",
+ "ooui-outline-control-remove": "אַראָפנעמען איינס",
+ "ooui-toolbar-more": "נאך",
+ "ooui-dialog-message-accept": "יאָ",
+ "ooui-dialog-message-reject": "אַנולירן",
+ "ooui-dialog-process-error": "עפעס איז דורכגעפאלן",
+ "ooui-dialog-process-dismiss": "צומאַכן",
+ "ooui-dialog-process-retry": "פרובירט נאכאמאל"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/yo.json b/vendor/oojs/oojs-ui/i18n/yo.json
new file mode 100644
index 00000000..d979fc13
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/yo.json
@@ -0,0 +1,10 @@
+{
+ "@metadata": {
+ "authors": [
+ "Demmy"
+ ]
+ },
+ "ooui-outline-control-move-down": "Sún onítòún sí sàlẹ̀",
+ "ooui-outline-control-move-up": "Sún onítòún s'ókè",
+ "ooui-toolbar-more": "Míràn"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/yue.json b/vendor/oojs/oojs-ui/i18n/yue.json
new file mode 100644
index 00000000..81ad9a95
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/yue.json
@@ -0,0 +1,16 @@
+{
+ "@metadata": {
+ "authors": [
+ "Deryck Chan"
+ ]
+ },
+ "ooui-outline-control-move-down": "向下搬",
+ "ooui-outline-control-move-up": "向上搬",
+ "ooui-outline-control-remove": "拎走",
+ "ooui-toolbar-more": "仲有...",
+ "ooui-dialog-message-accept": "好",
+ "ooui-dialog-message-reject": "取消",
+ "ooui-dialog-process-error": "唔對路",
+ "ooui-dialog-process-dismiss": "閂咗佢",
+ "ooui-dialog-process-retry": "再試過"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/zh-hans.json b/vendor/oojs/oojs-ui/i18n/zh-hans.json
new file mode 100644
index 00000000..ed2f61e4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/zh-hans.json
@@ -0,0 +1,34 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anakmalaysia",
+ "Bencmq",
+ "Demmy",
+ "Hydra",
+ "Hzy980512",
+ "Liangent",
+ "Liuxinyu970226",
+ "Qiyue2001",
+ "Shirayuki",
+ "Shizhao",
+ "TianyinLee",
+ "Xiaomingyan",
+ "Yfdyh000",
+ "Zhangjintao",
+ "乌拉跨氪",
+ "Great Brightstar"
+ ]
+ },
+ "ooui-outline-control-move-down": "项目下移",
+ "ooui-outline-control-move-up": "项目上移",
+ "ooui-outline-control-remove": "移除项目",
+ "ooui-toolbar-more": "更多",
+ "ooui-toolgroup-expand": "更多",
+ "ooui-toolgroup-collapse": "更少",
+ "ooui-dialog-message-accept": "确定",
+ "ooui-dialog-message-reject": "取消",
+ "ooui-dialog-process-error": "发生一些错误",
+ "ooui-dialog-process-dismiss": "解除",
+ "ooui-dialog-process-retry": "重试",
+ "ooui-dialog-process-continue": "继续"
+}
diff --git a/vendor/oojs/oojs-ui/i18n/zh-hant.json b/vendor/oojs/oojs-ui/i18n/zh-hant.json
new file mode 100644
index 00000000..3fd8d361
--- /dev/null
+++ b/vendor/oojs/oojs-ui/i18n/zh-hant.json
@@ -0,0 +1,32 @@
+{
+ "@metadata": {
+ "authors": [
+ "Anakmalaysia",
+ "Ch.Andrew",
+ "Hydra",
+ "Justincheng12345",
+ "Liflon",
+ "Liuxinyu970226",
+ "Qiyue2001",
+ "Radish10cm",
+ "Shirayuki",
+ "Simon Shek",
+ "Spring Roll Conan",
+ "Waihorace",
+ "Cwlin0416",
+ "LNDDYL"
+ ]
+ },
+ "ooui-outline-control-move-down": "項目下移",
+ "ooui-outline-control-move-up": "項目上移",
+ "ooui-outline-control-remove": "移除項目",
+ "ooui-toolbar-more": "更多",
+ "ooui-toolgroup-expand": "更多",
+ "ooui-toolgroup-collapse": "更少",
+ "ooui-dialog-message-accept": "確定",
+ "ooui-dialog-message-reject": "取消",
+ "ooui-dialog-process-error": "發生不明錯誤",
+ "ooui-dialog-process-dismiss": "關閉",
+ "ooui-dialog-process-retry": "再試一次",
+ "ooui-dialog-process-continue": "繼續"
+}
diff --git a/vendor/oojs/oojs-ui/jsduck.categories.json b/vendor/oojs/oojs-ui/jsduck.categories.json
new file mode 100644
index 00000000..c079f073
--- /dev/null
+++ b/vendor/oojs/oojs-ui/jsduck.categories.json
@@ -0,0 +1,80 @@
+[
+ {
+ "name": "OOjs UI",
+ "groups": [
+ {
+ "name": "General",
+ "classes": [
+ "OO.ui",
+ "OO.ui.Element",
+ "OO.ui.HtmlSnippet",
+ "OO.ui.Toolbar",
+ "OO.ui.Window",
+ "OO.ui.Dialog",
+ "OO.ui.WindowManager",
+ "OO.ui.Process",
+ "OO.ui.Error",
+ "OO.ui.ActionSet"
+ ]
+ },
+ {
+ "name": "Factories",
+ "classes": ["OO.ui.*Factory"]
+ },
+ {
+ "name": "Tools",
+ "classes": ["OO.ui.*Tool"]
+ },
+ {
+ "name": "Elements",
+ "classes": ["OO.ui.*Element"]
+ },
+ {
+ "name": "Layouts",
+ "classes": ["OO.ui.*Layout"]
+ },
+ {
+ "name": "Tool groups",
+ "classes": ["OO.ui.*ToolGroup"]
+ },
+ {
+ "name": "Widgets",
+ "classes": ["OO.ui.*Widget"]
+ },
+ {
+ "name": "Dialogs",
+ "classes": ["OO.ui.*Dialog"]
+ },
+ {
+ "name": "Themes",
+ "classes": ["OO.ui.*Theme"]
+ }
+ ]
+ },
+ {
+ "name": "Upstream",
+ "groups": [
+ {
+ "name": "OOJS",
+ "classes": ["OO", "OO.EventEmitter", "OO.Factory", "OO.Registry"]
+ },
+ {
+ "name": "jQuery",
+ "classes": ["jQuery", "jQuery.Event", "jQuery.Promise", "jQuery.Deferred", "jQuery.jqXHR"]
+ },
+ {
+ "name": "JavaScript",
+ "classes": [
+ "Array",
+ "Boolean",
+ "Date",
+ "Function",
+ "Number",
+ "Object",
+ "RegExp",
+ "String"
+ ]
+ }
+ ]
+ }
+]
diff --git a/vendor/oojs/oojs-ui/jsduck.eg-iframe.html b/vendor/oojs/oojs-ui/jsduck.eg-iframe.html
new file mode 100644
index 00000000..bd9c9dde
--- /dev/null
+++ b/vendor/oojs/oojs-ui/jsduck.eg-iframe.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <meta charset="UTF-8">
+ <title>OOjs UI example</title>
+ <!-- Prevent scaling on mobile devices which cause problems with dialog sizing -->
+ <meta name="viewport" content="width=device-width, user-scalable=no">
+ <style>
+ body {
+ font-size: 0.8em;
+ font-family: sans-serif;
+ }
+ </style>
+ <link rel="stylesheet" href="dist/oojs-ui-apex.css">
+</head>
+<body>
+ <script src="node_modules/jquery/dist/jquery.js"></script>
+ <script src="node_modules/oojs/dist/oojs.jquery.js"></script>
+ <script src="dist/oojs-ui.js"></script>
+ <script src="dist/oojs-ui-apex.js"></script>
+ <script>
+ function loadInlineExample( code, options, callback ) {
+ try {
+ eval( code );
+ callback && callback( true );
+ } catch (e) {
+ document.body.appendChild( document.createTextNode( e ) );
+ callback && callback( false, e );
+ }
+ }
+ </script>
+</body>
+</html>
diff --git a/vendor/oojs/oojs-ui/jsduck.external.js b/vendor/oojs/oojs-ui/jsduck.external.js
new file mode 100644
index 00000000..655c043f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/jsduck.external.js
@@ -0,0 +1,26 @@
+/**
+ * @class jQuery
+ * <http://api.jquery.com/>
+ */
+
+/**
+ * @class jQuery.Event
+ * <http://api.jquery.com/Types/#Event>
+ */
+
+/**
+ * @class jQuery.Promise
+ * <http://api.jquery.com/Types/#Promise>
+ */
+
+/**
+ * @class jQuery.Deferred
+ * @mixins jQuery.Promise
+ * <http://api.jquery.com/jQuery.Deferred/>
+ */
+
+/**
+ * @class jQuery.jqXHR
+ * @alternateClassName jqXHR
+ * <http://api.jquery.com/Types/#jqXHR>
+ */
diff --git a/vendor/oojs/oojs-ui/jsduck.json b/vendor/oojs/oojs-ui/jsduck.json
new file mode 100644
index 00000000..918b5702
--- /dev/null
+++ b/vendor/oojs/oojs-ui/jsduck.json
@@ -0,0 +1,16 @@
+{
+ "--title": "OOjs UI - Documentation",
+ "--output": "docs",
+ "--categories": "./jsduck.categories.json",
+ "--eg-iframe": "./jsduck.eg-iframe.html",
+ "--processes": "0",
+ "--warnings-exit-nonzero": true,
+ "--builtin-classes": true,
+ "--external": "HTMLDocument,Window,MouseEvent,KeyboardEvent",
+ "--warnings": ["-nodoc(class,public)"],
+ "--": [
+ "jsduck.external.js",
+ "node_modules/oojs/dist/oojs.jquery.js",
+ "src"
+ ]
+}
diff --git a/vendor/oojs/oojs-ui/php/Element.php b/vendor/oojs/oojs-ui/php/Element.php
new file mode 100644
index 00000000..eaa8c825
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Element.php
@@ -0,0 +1,297 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * DOM element abstraction.
+ *
+ * @abstract
+ */
+class Element extends Tag {
+
+ /* Static properties */
+
+ /**
+ * HTML tag name.
+ *
+ * This may be ignored if getTagName() is overridden.
+ *
+ * @var string
+ */
+ public static $tagName = 'div';
+
+ /**
+ * Default text direction, used for some layout calculations. Use setDefaultDir() to change.
+ *
+ * Currently only per-document directionality is supported.
+ *
+ * @var string
+ */
+ public static $defaultDir = 'ltr';
+
+ /* Members */
+
+ /**
+ * Element data.
+ *
+ * @var mixed
+ */
+ protected $data = null;
+
+ /**
+ * Mixins.
+ *
+ * @var array List mixed in objects.
+ */
+ protected $mixins = array();
+
+ /* Methods */
+
+ /**
+ * @param array $config Configuration options
+ * @param string[] $config['classes'] CSS class names to add
+ * @param string $config['id'] HTML id attribute
+ * @param string $config['text'] Text to insert
+ * @param array $config['content'] Content to append (after text), strings
+ * or Element objects. Strings will be HTML-escaped for output, use an
+ * HtmlSnippet instance to prevent that.
+ * @param mixed $config['data'] Element data
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $this->getTagName() );
+
+ // Initialization
+ if ( isset( $config['infusable'] ) && is_bool( $config['infusable'] ) ) {
+ $this->setInfusable( $config['infusable'] );
+ }
+ if ( isset( $config['data'] ) ) {
+ $this->setData( $config['data'] );
+ }
+ if ( isset( $config['classes'] ) && is_array( $config['classes'] ) ) {
+ $this->addClasses( $config['classes'] );
+ }
+ if ( isset( $config['id'] ) ) {
+ $this->setAttributes( array( 'id' => $config['id'] ) );
+ }
+ if ( isset( $config['text'] ) ) {
+ // JS compatibility
+ $this->appendContent( $config['text'] );
+ }
+ if ( isset( $config['content'] ) ) {
+ $this->appendContent( $config['content'] );
+ }
+ }
+
+ /**
+ * Call a mixed-in method.
+ *
+ * This makes the methods of a mixin accessible through the element being mixed into.
+ *
+ * Triggers an error if the method is not found, as normal.
+ *
+ * @param string $method Method name
+ * @param array $arguments Method arguments
+ * @return mixed Result of method call
+ */
+ public function __call( $method, $arguments ) {
+ // Search mixins for methods
+ foreach ( $this->mixins as $mixin ) {
+ if ( method_exists( $mixin, $method ) ) {
+ return call_user_func_array( array( $mixin, $method ), $arguments );
+ }
+ }
+ // Fail normally
+ trigger_error(
+ 'Call to undefined method ' . __CLASS__ . '::' . $method . '()',
+ E_USER_ERROR
+ );
+ }
+
+ /**
+ * Get a mixed-in target property.
+ *
+ * This makes the target of a mixin accessible through the element being mixed into.
+ *
+ * The target's property name is statically configured by the mixin class.
+ *
+ * Triggers a notice if the property is not found, as normal.
+ *
+ * @param string $name Property name
+ * @return Tag|null Target property or null if not found
+ */
+ public function __get( $name ) {
+ // Search mixins for methods
+ foreach ( $this->mixins as $mixin ) {
+ if ( isset( $mixin::$targetPropertyName ) && $mixin::$targetPropertyName === $name ) {
+ return $mixin->target;
+ }
+ }
+ // Fail normally
+ trigger_error( 'Undefined property: ' . $name, E_USER_NOTICE );
+ return null;
+ }
+
+ /**
+ * Get the HTML tag name.
+ *
+ * Override this method to base the result on instance information.
+ *
+ * @return string HTML tag name
+ */
+ public function getTagName() {
+ return $this::$tagName;
+ }
+
+ /**
+ * Get element data.
+ *
+ * @return mixed Element data
+ */
+ public function getData() {
+ return $this->data;
+ }
+
+ /**
+ * Set element data.
+ *
+ * @param mixed $data Element data
+ * @chainable
+ */
+ public function setData( $data ) {
+ $this->data = $data;
+ return $this;
+ }
+
+ /**
+ * Check if element supports one or more methods.
+ *
+ * @param string|string[] $methods Method or list of methods to check
+ * @return boolean All methods are supported
+ */
+ public function supports( $methods ) {
+ $support = 0;
+ $methods = (array)$methods;
+
+ foreach ( $methods as $method ) {
+ if ( method_exists( $this, $method ) ) {
+ $support++;
+ continue;
+ }
+
+ // Search mixins for methods
+ foreach ( $this->mixins as $mixin ) {
+ if ( method_exists( $mixin, $method ) ) {
+ $support++;
+ break;
+ }
+ }
+ }
+
+ return count( $methods ) === $support;
+ }
+
+ /**
+ * Mixin a class.
+ *
+ * @param ElementMixin $mixin Mixin object
+ */
+ public function mixin( ElementMixin $mixin ) {
+ $this->mixins[] = $mixin;
+ }
+
+ /**
+ * Add the necessary properties to the given `$config` array to allow
+ * reconstruction of this widget via its constructor.
+ * @param array &$config
+ * An array which will be mutated to add the necessary configuration
+ * properties. Unless you are implementing a subclass, you should
+ * always pass a new empty `array()`.
+ * @return array
+ * A configuration array which can be passed to this object's
+ * constructor to recreate it. This is a return value to allow
+ * the safe use of copy-by-value functions like `array_merge` in
+ * the implementation.
+ */
+ public function getConfig( &$config ) {
+ foreach ( $this->mixins as $mixin ) {
+ $config = $mixin->getConfig( $config );
+ }
+ if ( $this->data !== null ) {
+ $config['data'] = $this->data;
+ }
+ return $config;
+ }
+
+ /**
+ * Create a modified version of the configuration array suitable for
+ * JSON serialization by replacing `Tag` references and
+ * `HtmlSnippet`s.
+ *
+ * @return array
+ * A serialized configuration array.
+ */
+ private function getSerializedConfig() {
+ // Ensure that '_' comes first in the output.
+ $config = array( '_' => true );
+ $config = $this->getConfig( $config );
+ // Post-process config array to turn Tag references into ID references
+ // and HtmlSnippet references into a { html: 'string' } JSON form.
+ $replaceElements = function( &$item ) {
+ if ( $item instanceof Tag ) {
+ $item->ensureInfusableId();
+ $item = array( 'tag' => $item->getAttribute( 'id' ) );
+ } elseif ( $item instanceof HtmlSnippet ) {
+ $item = array( 'html' => (string) $item );
+ }
+ };
+ array_walk_recursive( $config, $replaceElements );
+ // Set '_' last to ensure that subclasses can't accidentally step on it.
+ $config['_'] = preg_replace( '/^OOUI\\\\/', '', get_class( $this ) );
+ return $config;
+ }
+
+ protected function getGeneratedAttributes() {
+ $attributesArray = parent::getGeneratedAttributes();
+ // Add `data-ooui` attribute from serialized config array.
+ if ( $this->infusable ) {
+ $serialized = $this->getSerializedConfig();
+ $attributesArray['data-ooui'] = json_encode( $serialized );
+ }
+ return $attributesArray;
+ }
+
+ /**
+ * Render element into HTML.
+ *
+ * @return string HTML serialization
+ */
+ public function toString() {
+ Theme::singleton()->updateElementClasses( $this );
+ if ( $this->isInfusable() ) {
+ $this->ensureInfusableId();
+ }
+ return parent::toString();
+ }
+
+ /**
+ * Get the direction of the user interface for a given element.
+ *
+ * Currently only per-document directionality is supported.
+ *
+ * @param Tag $element Element to check
+ * @return string Text direction, either 'ltr' or 'rtl'
+ */
+ public static function getDir( Tag $element ) {
+ return self::$defaultDir;
+ }
+
+ /**
+ * Set the default direction of the user interface.
+ *
+ * @return string Text direction, either 'ltr' or 'rtl'
+ */
+ public static function setDefaultDir( $dir ) {
+ self::$defaultDir = $dir === 'rtl' ? 'rtl' : 'ltr';
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/ElementMixin.php b/vendor/oojs/oojs-ui/php/ElementMixin.php
new file mode 100644
index 00000000..7f54076e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/ElementMixin.php
@@ -0,0 +1,55 @@
+<?php
+
+namespace OOUI;
+
+class ElementMixin {
+
+ /* Properties */
+
+ /**
+ * Tag being targeted.
+ *
+ * @var Tag
+ */
+ public $target = null;
+
+ /**
+ * Element being mixed into.
+ *
+ * @var Element
+ */
+ protected $element = null;
+
+ /**
+ * Property name for accessing the target on the element.
+ *
+ * @var string
+ */
+ public static $targetPropertyName = '';
+
+ /* Methods */
+
+ /**
+ * Create element.
+ *
+ * @param Element $element Element being mixed into
+ * @param Tag $tag Tag being targeted
+ * @param array $config Configuration options
+ */
+ public function __construct( Element $element, Tag $target, array $config = array() ) {
+ $this->element = $element;
+ $this->target = $target;
+ }
+
+ /**
+ * Add properties to the given `$config` array to allow reconstruction
+ * of this widget via its constructor. This method is meant to be
+ * overridden by subclasses of `ElementMixin`.
+ *
+ * @return array A configuration array.
+ * @see Element::getConfig()
+ */
+ public function getConfig( &$config ) {
+ return $config;
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Exception.php b/vendor/oojs/oojs-ui/php/Exception.php
new file mode 100644
index 00000000..2f5ba1b4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Exception.php
@@ -0,0 +1,6 @@
+<?php
+
+namespace OOUI;
+
+class Exception extends \Exception {
+}
diff --git a/vendor/oojs/oojs-ui/php/HtmlSnippet.php b/vendor/oojs/oojs-ui/php/HtmlSnippet.php
new file mode 100644
index 00000000..e0889fca
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/HtmlSnippet.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Wraps a HTML snippet for use with Tag::appendContent() and Tag::prependContent().
+ */
+class HtmlSnippet {
+
+ /* Members */
+
+ /**
+ * HTML snippet this instance represents.
+ *
+ * @var string
+ */
+ protected $content;
+
+ /* Methods */
+
+ /**
+ * @param string $content
+ */
+ public function __construct( $content ) {
+ $this->content = $content;
+ }
+
+ /**
+ * Render into HTML.
+ *
+ * @return string Unchanged HTML snippet
+ */
+ public function __toString() {
+ return $this->content;
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Layout.php b/vendor/oojs/oojs-ui/php/Layout.php
new file mode 100644
index 00000000..7a4e58d0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Layout.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Container for elements.
+ *
+ * @abstract
+ */
+class Layout extends Element {
+ /**
+ * @param array $config Configuration options
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-layout' ) );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Tag.php b/vendor/oojs/oojs-ui/php/Tag.php
new file mode 100644
index 00000000..da8c2bfa
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Tag.php
@@ -0,0 +1,365 @@
+<?php
+
+namespace OOUI;
+
+class Tag {
+
+ /* Members */
+
+ /**
+ * Tag name for this instance.
+ *
+ * @var string HTML tag name
+ */
+ protected $tag = '';
+
+ /**
+ * Attributes.
+ *
+ * @var array HTML attributes
+ */
+ protected $attributes = array();
+
+ /**
+ * Classes.
+ *
+ * @var array CSS classes
+ */
+ protected $classes = array();
+
+ /**
+ * Content.
+ *
+ * @var array Content text and elements
+ */
+ protected $content = array();
+
+ /**
+ * Group.
+ *
+ * @var GroupElement|null Group element is in
+ */
+ protected $elementGroup = null;
+
+ /**
+ * Infusion support.
+ *
+ * @var boolean Whether to serialize tag/element/widget state for client-side use.
+ */
+ protected $infusable = false;
+
+ /* Methods */
+
+ /**
+ * Create element.
+ *
+ * @param string $tag HTML tag name
+ */
+ public function __construct( $tag = 'div' ) {
+ $this->tag = $tag;
+ }
+
+ /**
+ * Check for CSS class.
+ *
+ * @param string $name CSS class name
+ * @return boolean
+ */
+ public function hasClass( $class ) {
+ return in_array( $class, $this->classes );
+ }
+
+ /**
+ * Add CSS classes.
+ *
+ * @param array $classes List of classes to add
+ * @chainable
+ */
+ public function addClasses( array $classes ) {
+ $this->classes = array_merge( $this->classes, $classes );
+ return $this;
+ }
+
+ /**
+ * Remove CSS classes.
+ *
+ * @param array $classes List of classes to remove
+ * @chainable
+ */
+ public function removeClasses( array $classes ) {
+ $this->classes = array_diff( $this->classes, $classes );
+ return $this;
+ }
+
+ /**
+ * Toggle CSS classes.
+ *
+ * @param array $classes List of classes to add
+ * @param boolean $toggle Add classes
+ * @chainable
+ */
+ public function toggleClasses( array $classes, $toggle = null ) {
+ if ( $toggle === null ) {
+ $this->classes = array_diff(
+ array_merge( $this->classes, $classes ),
+ array_intersect( $this->classes, $classes )
+ );
+ } elseif ( $toggle ) {
+ $this->classes = array_merge( $this->classes, $classes );
+ } else {
+ $this->classes = array_diff( $this->classes, $classes );
+ }
+ return $this;
+ }
+
+ /**
+ * Get HTML attribute value.
+ *
+ * @param string $key HTML attribute name
+ * @return string|null
+ */
+ public function getAttribute( $key ) {
+ return isset( $this->attributes[$key] ) ? $this->attributes[$key] : null;
+ }
+
+ /**
+ * Add HTML attributes.
+ *
+ * @param array $attributes List of attribute key/value pairs to add
+ * @chainable
+ */
+ public function setAttributes( array $attributes ) {
+ foreach ( $attributes as $key => $value ) {
+ $this->attributes[$key] = $value;
+ }
+ return $this;
+ }
+
+ /**
+ * Set value of input element ('value' attribute for most, element content for textarea).
+ *
+ * @param string $value Value to set
+ * @chainable
+ */
+ public function setValue( $value ) {
+ if ( strtolower( $this->tag ) === 'textarea' ) {
+ $this->clearContent();
+ $this->appendContent( $value );
+ } else {
+ $this->setAttributes( array( 'value' => $value ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Remove HTML attributes.
+ *
+ * @param array $keys List of attribute keys to remove
+ * @chainable
+ */
+ public function removeAttributes( array $keys ) {
+ foreach ( $keys as $key ) {
+ unset( $this->attributes[$key] );
+ }
+ return $this;
+ }
+
+ /**
+ * Add content to the end.
+ *
+ * Accepts variadic arguments (the $content argument can be repeated any number of times).
+ *
+ * @param string|Tag|HtmlSnippet $content Content to append. Strings will be HTML-escaped
+ * for output, use a HtmlSnippet instance to prevent that.
+ * @chainable
+ */
+ public function appendContent( /* $content... */ ) {
+ $contents = func_get_args();
+ $this->content = array_merge( $this->content, $contents );
+ return $this;
+ }
+
+ /**
+ * Add content to the beginning.
+ *
+ * Accepts variadic arguments (the $content argument can be repeated any number of times).
+ *
+ * @param string|Tag|HtmlSnippet $content Content to prepend. Strings will be HTML-escaped
+ * for output, use a HtmlSnippet instance to prevent that.
+ * @chainable
+ */
+ public function prependContent( /* $content... */ ) {
+ $contents = func_get_args();
+ array_splice( $this->content, 0, 0, $contents );
+ return $this;
+ }
+
+ /**
+ * Remove all content.
+ *
+ * @chainable
+ */
+ public function clearContent() {
+ $this->content = array();
+ return $this;
+ }
+
+ /**
+ * Get group element is in.
+ *
+ * @return GroupElement|null Group element, null if none
+ */
+ public function getElementGroup() {
+ return $this->elementGroup;
+ }
+
+ /**
+ * Set group element is in.
+ *
+ * @param GroupElement|null $group Group element, null if none
+ * @chainable
+ */
+ public function setElementGroup( $group ) {
+ $this->elementGroup = $group;
+ return $this;
+ }
+
+ /**
+ * Enable widget for client-side infusion.
+ *
+ * @param boolean $infusable True to allow tag/element/widget to be referenced client-side.
+ * @chainable
+ */
+ public function setInfusable( $infusable ) {
+ $this->infusable = $infusable;
+ return $this;
+ }
+
+ /**
+ * Get client-side infusability.
+ *
+ * @return boolean If this tag/element/widget can be referenced client-side.
+ */
+ public function isInfusable() {
+ return $this->infusable;
+ }
+
+ private static $id_cnt = 0;
+ /**
+ * Ensure that this given Tag is infusable and has a unique `id`
+ * attribute.
+ * @chainable
+ */
+ public function ensureInfusableId() {
+ $this->setInfusable( true );
+ if ( $this->getAttribute( 'id' ) === null ) {
+ $this->setAttributes( array( 'id' => "ooui-" . ( self::$id_cnt++ ) ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Return an augmented `attributes` array, including synthetic attributes
+ * which are created from other properties (like the `classes` array)
+ * but which shouldn't be retained in the user-visible `attributes`.
+ * @return array An attributes array.
+ */
+ protected function getGeneratedAttributes() {
+ // Copy attributes, add `class` attribute from `$this->classes` array.
+ $attributesArray = $this->attributes;
+ if ( $this->classes ) {
+ $attributesArray['class'] = implode( ' ', array_unique( $this->classes ) );
+ }
+ if ( $this->infusable ) {
+ // Indicate that this is "just" a tag (not a widget)
+ $attributesArray['data-ooui'] = json_encode( array( '_' => 'Tag' ) );
+ }
+ return $attributesArray;
+ }
+
+ /**
+ * Render element into HTML.
+ *
+ * @return string HTML serialization
+ */
+ public function toString() {
+ $attributes = '';
+ foreach ( $this->getGeneratedAttributes() as $key => $value ) {
+ if ( !preg_match( '/^[0-9a-zA-Z-]+$/', $key ) ) {
+ throw new Exception( 'Attribute name must consist of only ASCII letters, numbers and dash' );
+ }
+
+ if ( $key === 'href' || $key === 'action' ) {
+ // Make it impossible to point a link or a form to a 'javascript:' URL. There's no good way
+ // to blacklist them because of very lax parsing, so instead we whitelist known-good
+ // protocols (and also accept protocol-less and protocol-relative links). There are no good
+ // reasons to ever use 'javascript:' URLs anyway.
+ $protocolWhitelist = array(
+ // Sourced from MediaWiki's $wgUrlProtocols
+ 'bitcoin', 'ftp', 'ftps', 'geo', 'git', 'gopher', 'http', 'https', 'irc', 'ircs',
+ 'magnet', 'mailto', 'mms', 'news', 'nntp', 'redis', 'sftp', 'sip', 'sips', 'sms', 'ssh',
+ 'svn', 'tel', 'telnet', 'urn', 'worldwind', 'xmpp',
+ );
+
+ // Protocol-relative URLs are handled really badly by parse_url()
+ if ( substr( $value, 0, 2 ) === '//' ) {
+ $url = "http:$value";
+ } else {
+ $url = $value;
+ }
+ // Must suppress warnings when the value is not a valid URL. parse_url() returns false then.
+ // @codingStandardsIgnoreStart
+ $scheme = @parse_url( $url, PHP_URL_SCHEME );
+ // @codingStandardsIgnoreEnd
+
+ if ( !( $scheme === null || in_array( strtolower( $scheme ), $protocolWhitelist ) ) ) {
+ throw new Exception( "Potentially unsafe '$key' attribute value. " .
+ "Scheme: '$scheme'; value: '$value'." );
+ }
+ }
+
+ // Use single-quotes around the attribute value in HTML, because
+ // some of the values might be JSON strings
+ // 1. Encode both single and double quotes (and other special chars)
+ $value = htmlspecialchars( $value, ENT_QUOTES );
+ // 2. Decode double quotes, for readability.
+ $value = str_replace( '&quot;', '"', $value );
+ // 3. Wrap attribute value in single quotes in the HTML.
+ $attributes .= ' ' . $key . "='" . $value . "'";
+ }
+
+ // Content
+ $content = '';
+ foreach ( $this->content as $part ) {
+ if ( is_string( $part ) ) {
+ $content .= htmlspecialchars( $part );
+ } elseif ( $part instanceof Tag || $part instanceof HtmlSnippet ) {
+ $content .= (string)$part;
+ }
+ }
+
+ if ( !preg_match( '/^[0-9a-zA-Z]+$/', $this->tag ) ) {
+ throw new Exception( 'Tag name must consist of only ASCII letters and numbers' );
+ }
+
+ // Tag
+ return '<' . $this->tag . $attributes . '>' . $content . '</' . $this->tag . '>';
+ }
+
+ /**
+ * Magic method implementation.
+ *
+ * PHP doesn't allow __toString to throw exceptions and will trigger a fatal error if it does.
+ * This is a wrapper around the real toString() to convert them to errors instead.
+ *
+ * @return string
+ */
+ public function __toString() {
+ try {
+ return $this->toString();
+ } catch ( Exception $ex ) {
+ trigger_error( (string)$ex, E_USER_ERROR );
+ return '';
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Theme.php b/vendor/oojs/oojs-ui/php/Theme.php
new file mode 100644
index 00000000..d36b6d82
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Theme.php
@@ -0,0 +1,58 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Theme logic.
+ *
+ * @abstract
+ */
+class Theme {
+
+ /* Members */
+
+ private static $singleton;
+
+ /* Static Methods */
+
+ public static function setSingleton( Theme $theme ) {
+ self::$singleton = $theme;
+ }
+
+ public static function singleton() {
+ if ( !self::$singleton ) {
+ throw new Exception( __METHOD__ . ' was called with no singleton theme set.' );
+ }
+
+ return self::$singleton;
+ }
+
+ /**
+ * Get a list of classes to be applied to a widget.
+ *
+ * The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes,
+ * otherwise state transitions will not work properly.
+ *
+ * @param Element $element Element for which to get classes
+ * @return array Categorized class names with `on` and `off` lists
+ */
+ public function getElementClasses( Element $element ) {
+ return array( 'on' => array(), 'off' => array() );
+ }
+
+ /**
+ * Update CSS classes provided by the theme.
+ *
+ * For elements with theme logic hooks, this should be called any time there's a state change.
+ *
+ * @param Element $element Element for which to update classes
+ * @return array Categorized class names with `on` and `off` lists
+ */
+ public function updateElementClasses( Element $element ) {
+ $classes = $this->getElementClasses( $element );
+
+ $element
+ ->removeClasses( $classes['off'] )
+ ->addClasses( $classes['on'] );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/Widget.php b/vendor/oojs/oojs-ui/php/Widget.php
new file mode 100644
index 00000000..04152aa5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/Widget.php
@@ -0,0 +1,71 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * User interface control.
+ *
+ * @abstract
+ */
+class Widget extends Element {
+
+ /* Properties */
+
+ /**
+ * Disabled.
+ *
+ * @var boolean Widget is disabled
+ */
+ protected $disabled = false;
+
+ /* Methods */
+
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['disabled'] Disable (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Initialize config
+ $config = array_merge( array( 'disabled' => false ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-widget' ) );
+ $this->setDisabled( $config['disabled'] );
+ }
+
+ /**
+ * Check if the widget is disabled.
+ *
+ * @return boolean Button is disabled
+ */
+ public function isDisabled() {
+ return $this->disabled;
+ }
+
+ /**
+ * Set the disabled state of the widget.
+ *
+ * This should probably change the widgets' appearance and prevent it from being used.
+ *
+ * @param boolean $disabled Disable widget
+ * @chainable
+ */
+ public function setDisabled( $disabled ) {
+ $this->disabled = !!$disabled;
+ $this->toggleClasses( array( 'oo-ui-widget-disabled' ), $this->disabled );
+ $this->toggleClasses( array( 'oo-ui-widget-enabled' ), !$this->disabled );
+ $this->setAttributes( array( 'aria-disabled' => $this->disabled ? 'true' : 'false' ) );
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->disabled ) {
+ $config['disabled'] = $this->disabled;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/ButtonElement.php b/vendor/oojs/oojs-ui/php/elements/ButtonElement.php
new file mode 100644
index 00000000..f9acf2d8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/ButtonElement.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element with a button.
+ *
+ * Buttons are used for controls which can be clicked. They can be configured to use tab indexing
+ * and access keys for accessibility purposes.
+ *
+ * @abstract
+ */
+class ButtonElement extends ElementMixin {
+ /**
+ * Button is framed.
+ *
+ * @var boolean
+ */
+ protected $framed = false;
+
+ /**
+ * Button's access key.
+ *
+ * @var string
+ */
+ protected $accessKey = null;
+
+ public static $targetPropertyName = 'button';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param boolean $config['framed'] Render button with a frame (default: true)
+ * @param string $config['accessKey'] Button's access key
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['button'] ) ? $config['button'] : new Tag( 'a' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->element->addClasses( array( 'oo-ui-buttonElement' ) );
+ $this->target->addClasses( array( 'oo-ui-buttonElement-button' ) );
+ $this->toggleFramed( isset( $config['framed'] ) ? $config['framed'] : true );
+ $this->setAccessKey( isset( $config['accessKey'] ) ? $config['accessKey'] : null );
+ $this->target->setAttributes( array(
+ 'role' => 'button',
+ ) );
+ }
+
+ /**
+ * Toggle frame.
+ *
+ * @param boolean $framed Make button framed, omit to toggle
+ * @chainable
+ */
+ public function toggleFramed( $framed = null ) {
+ $this->framed = $framed !== null ? !!$framed : !$this->framed;
+ $this->element->toggleClasses( array( 'oo-ui-buttonElement-framed' ), $this->framed );
+ $this->element->toggleClasses( array( 'oo-ui-buttonElement-frameless' ), !$this->framed );
+ }
+
+ /**
+ * Check if button has a frame.
+ *
+ * @return boolean Button is framed
+ */
+ public function isFramed() {
+ return $this->framed;
+ }
+
+ /**
+ * Set access key.
+ *
+ * @param string $accessKey Button's access key, use empty string to remove
+ * @chainable
+ */
+ public function setAccessKey( $accessKey ) {
+ $accessKey = is_string( $accessKey ) && strlen( $accessKey ) ? $accessKey : null;
+
+ if ( $this->accessKey !== $accessKey ) {
+ if ( $accessKey !== null ) {
+ $this->target->setAttributes( array( 'accesskey' => $accessKey ) );
+ } else {
+ $this->target->removeAttributes( array( 'accesskey' ) );
+ }
+ $this->accessKey = $accessKey;
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->framed !== true ) {
+ $config['framed'] = $this->framed;
+ }
+ if ( $this->accessKey !== null ) {
+ $config['accessKey'] = $this->accessKey;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/FlaggedElement.php b/vendor/oojs/oojs-ui/php/elements/FlaggedElement.php
new file mode 100644
index 00000000..bd5dc80d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/FlaggedElement.php
@@ -0,0 +1,133 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element with named flags that can be added, removed, listed and checked.
+ *
+ * A flag, when set, adds a CSS class on the `$element` by combining `oo-ui-flaggedElement-` with
+ * the flag name. Flags are primarily useful for styling.
+ *
+ * @abstract
+ */
+class FlaggedElement extends ElementMixin {
+ /**
+ * Flags.
+ *
+ * @var string
+ */
+ protected $flags = array();
+
+ public static $targetPropertyName = 'flagged';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string|string[] $config['flags'] Flags describing importance and functionality, e.g.
+ * 'primary', 'safe', 'progressive', 'destructive' or 'constructive'
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['flagged'] ) ? $config['flagged'] : $element;
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->setFlags( isset( $config['flags'] ) ? $config['flags'] : null );
+ }
+
+ /**
+ * Check if a flag is set.
+ *
+ * @param string $flag Name of flag
+ * @return boolean Has flag
+ */
+ public function hasFlag( $flag ) {
+ return isset( $this->flags[$flag] );
+ }
+
+ /**
+ * Get the names of all flags set.
+ *
+ * @return string[] Flag names
+ */
+ public function getFlags() {
+ return array_keys( $this->flags );
+ }
+
+ /**
+ * Clear all flags.
+ *
+ * @chainable
+ */
+ public function clearFlags() {
+ $remove = array();
+ $classPrefix = 'oo-ui-flaggedElement-';
+
+ foreach ( $this->flags as $flag ) {
+ $remove[] = $classPrefix . $flag;
+ }
+
+ $this->target->removeClasses( $remove );
+ $this->flags = array();
+
+ return $this;
+ }
+
+ /**
+ * Add one or more flags.
+ *
+ * @param string|array $flags One or more flags to add, or an array keyed by flag name
+ * containing boolean set/remove instructions.
+ * @chainable
+ */
+ public function setFlags( $flags ) {
+ $add = array();
+ $remove = array();
+ $classPrefix = 'oo-ui-flaggedElement-';
+
+ if ( is_string( $flags ) ) {
+ // Set
+ if ( !isset( $this->flags[$flags] ) ) {
+ $this->flags[$flags] = true;
+ $add[] = $classPrefix . $flags;
+ }
+ } elseif ( is_array( $flags ) ) {
+ foreach ( $flags as $key => $value ) {
+ if ( is_numeric( $key ) ) {
+ // Set
+ if ( !isset( $this->flags[$value] ) ) {
+ $this->flags[$value] = true;
+ $add[] = $classPrefix . $value;
+ }
+ } else {
+ if ( $value ) {
+ // Set
+ if ( !isset( $this->flags[$key] ) ) {
+ $this->flags[$key] = true;
+ $add[] = $classPrefix . $key;
+ }
+ } else {
+ // Remove
+ if ( isset( $this->flags[$key] ) ) {
+ unset( $this->flags[$key] );
+ $remove[] = $classPrefix . $key;
+ }
+ }
+ }
+ }
+ }
+
+ $this->target
+ ->addClasses( $add )
+ ->removeClasses( $remove );
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( !empty( $this->flags ) ) {
+ $config['flags'] = $this->getFlags();
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/GroupElement.php b/vendor/oojs/oojs-ui/php/elements/GroupElement.php
new file mode 100644
index 00000000..93d3c7a1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/GroupElement.php
@@ -0,0 +1,129 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing a sequence of child elements.
+ *
+ * @abstract
+ */
+class GroupElement extends ElementMixin {
+ /**
+ * List of items in the group.
+ *
+ * @var Element[]
+ */
+ protected $items = array();
+
+ public static $targetPropertyName = 'group';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['group'] ) ? $config['group'] : new Tag( 'div' );
+ parent::__construct( $element, $target, $config );
+ }
+
+ /**
+ * Check if there are no items.
+ *
+ * @return boolean Group is empty
+ */
+ public function isEmpty() {
+ return !count( $this->items );
+ }
+
+ /**
+ * Get items.
+ *
+ * @return Element[] Items
+ */
+ public function getItems() {
+ return $this->items;
+ }
+
+ /**
+ * Add items.
+ *
+ * Adding an existing item will move it.
+ *
+ * @param Element[] $items Items
+ * @param number $index Index to insert items at
+ * @chainable
+ */
+ public function addItems( array $items, $index = null ) {
+ foreach ( $items as $item ) {
+ // Check if item exists then remove it first, effectively "moving" it
+ $currentIndex = array_search( $item, $this->items );
+ if ( $currentIndex !== false ) {
+ $this->removeItems( array( $item ) );
+ // Adjust index to compensate for removal
+ if ( $currentIndex < $index ) {
+ $index--;
+ }
+ }
+ // Add the item
+ $item->setElementGroup( $this );
+ }
+
+ if ( $index === null || $index < 0 || $index >= count( $this->items ) ) {
+ $this->items = array_merge( $this->items, $items );
+ } else {
+ array_splice( $this->items, $index, 0, $items );
+ }
+
+ // Update actual target element contents to reflect our list
+ $this->target->clearContent();
+ call_user_func_array( array( $this->target, 'appendContent' ), $this->items );
+
+ return $this;
+ }
+
+ /**
+ * Remove items.
+ *
+ * @param Element[] $items Items to remove
+ * @chainable
+ */
+ public function removeItems( $items ) {
+ foreach ( $items as $item ) {
+ $index = array_search( $item, $this->items );
+ if ( $index !== false ) {
+ $item->setElementGroup( null );
+ array_splice( $this->items, $index, 1 );
+ }
+ }
+
+ // Update actual target element contents to reflect our list
+ $this->target->clearContent();
+ call_user_func_array( array( $this->target, 'appendContent' ), $this->items );
+
+ return $this;
+ }
+
+ /**
+ * Clear all items.
+ *
+ * Items will be detached, not removed, so they can be used later.
+ *
+ * @chainable
+ */
+ public function clearItems() {
+ foreach ( $this->items as $item ) {
+ $item->setElementGroup( null );
+ }
+
+ $this->items = array();
+ $this->target->clearContent();
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $config['items'] = $this->items;
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/IconElement.php b/vendor/oojs/oojs-ui/php/elements/IconElement.php
new file mode 100644
index 00000000..b6d27376
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/IconElement.php
@@ -0,0 +1,76 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing an icon.
+ *
+ * Icons are graphics, about the size of normal text. They can be used to aid the user in locating
+ * a control or convey information in a more space efficient way. Icons should rarely be used
+ * without labels; such as in a toolbar where space is at a premium or within a context where the
+ * meaning is very clear to the user.
+ *
+ * @abstract
+ */
+class IconElement extends ElementMixin {
+ /**
+ * Symbolic icon name.
+ *
+ * @var string
+ */
+ protected $icon = null;
+
+ public static $targetPropertyName = 'icon';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string $config['icon'] Symbolic icon name
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ // FIXME 'iconElement' is a very stupid way to call '$icon'
+ $target = isset( $config['iconElement'] ) ? $config['iconElement'] : new Tag( 'span' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->target->addClasses( array( 'oo-ui-iconElement-icon' ) );
+ $this->setIcon( isset( $config['icon'] ) ? $config['icon'] : null );
+ }
+
+ /**
+ * Set icon name.
+ *
+ * @param string|null $icon Symbolic icon name
+ * @chainable
+ */
+ public function setIcon( $icon = null ) {
+ if ( $this->icon !== null ) {
+ $this->target->removeClasses( array( 'oo-ui-icon-' . $this->icon ) );
+ }
+ if ( $icon !== null ) {
+ $this->target->addClasses( array( 'oo-ui-icon-' . $icon ) );
+ }
+
+ $this->icon = $icon;
+ $this->element->toggleClasses( array( 'oo-ui-iconElement' ), (bool)$this->icon );
+
+ return $this;
+ }
+
+ /**
+ * Get icon name.
+ *
+ * @return string Icon name
+ */
+ public function getIcon() {
+ return $this->icon;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->icon !== null ) {
+ $config['icon'] = $this->icon;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/IndicatorElement.php b/vendor/oojs/oojs-ui/php/elements/IndicatorElement.php
new file mode 100644
index 00000000..56238b6c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/IndicatorElement.php
@@ -0,0 +1,78 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing an indicator.
+ *
+ * Indicators are graphics, smaller than normal text. They can be used to describe unique status or
+ * behavior. Indicators should only be used in exceptional cases; such as a button that opens a menu
+ * instead of performing an action directly, or an item in a list which has errors that need to be
+ * resolved.
+ *
+ * @abstract
+ */
+class IndicatorElement extends ElementMixin {
+ /**
+ * Symbolic indicator name
+ *
+ * @var string|null
+ */
+ protected $indicator = null;
+
+ public static $targetPropertyName = 'indicator';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string $config['indicator'] Symbolic indicator name
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ // FIXME 'indicatorElement' is a very stupid way to call '$indicator'
+ $target = isset( $config['indicatorElement'] )
+ ? $config['indicatorElement']
+ : new Tag( 'span' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->target->addClasses( array( 'oo-ui-indicatorElement-indicator' ) );
+ $this->setIndicator( isset( $config['indicator'] ) ? $config['indicator'] : null );
+ }
+
+ /**
+ * Set indicator name.
+ *
+ * @param string|null $indicator Symbolic name of indicator to use or null for no indicator
+ * @chainable
+ */
+ public function setIndicator( $indicator = null ) {
+ if ( $this->indicator !== null ) {
+ $this->target->removeClasses( array( 'oo-ui-indicator-' . $this->indicator ) );
+ }
+ if ( $indicator !== null ) {
+ $this->target->addClasses( array( 'oo-ui-indicator-' . $indicator ) );
+ }
+
+ $this->indicator = $indicator;
+ $this->element->toggleClasses( array( 'oo-ui-indicatorElement' ), (bool)$this->indicator );
+
+ return $this;
+ }
+
+ /**
+ * Get indicator name.
+ *
+ * @return string Symbolic name of indicator
+ */
+ public function getIndicator() {
+ return $this->indicator;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->indicator !== null ) {
+ $config['indicator'] = $this->indicator;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/LabelElement.php b/vendor/oojs/oojs-ui/php/elements/LabelElement.php
new file mode 100644
index 00000000..d5cf7bee
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/LabelElement.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element containing a label.
+ *
+ * @abstract
+ */
+class LabelElement extends ElementMixin {
+ /**
+ * Label value.
+ *
+ * @var string|HtmlSnippet|null
+ */
+ protected $label = null;
+
+ public static $targetPropertyName = 'label';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string|HtmlSnippet $config['label'] Label text
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ // FIXME 'labelElement' is a very stupid way to call '$label'
+ $target = isset( $config['labelElement'] ) ? $config['labelElement'] : new Tag( 'span' );
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->target->addClasses( array( 'oo-ui-labelElement-label' ) );
+ $this->setLabel( isset( $config['label'] ) ? $config['label'] : null );
+ }
+
+ /**
+ * Set the label.
+ *
+ * An empty string will result in the label being hidden. A string containing only whitespace will
+ * be converted to a single `&nbsp;`.
+ *
+ * @param string|HtmlSnippet|null $label Label text
+ * @chainable
+ */
+ public function setLabel( $label ) {
+ $this->label = $label;
+
+ $this->target->clearContent();
+ if ( $this->label !== null ) {
+ if ( is_string( $this->label ) && $this->label !== '' && trim( $this->label ) === '' ) {
+ $this->target->appendContent( new HtmlSnippet( '&nbsp;' ) );
+ } else {
+ $this->target->appendContent( $label );
+ }
+ }
+
+ $this->element->toggleClasses( array( 'oo-ui-labelElement' ), !!$this->label );
+
+ return $this;
+ }
+
+ /**
+ * Get the label.
+ *
+ * @return string|HtmlSnippet|null Label text
+ */
+ public function getLabel() {
+ return $this->label;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->label !== null ) {
+ $config['label'] = $this->label;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php b/vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php
new file mode 100644
index 00000000..223b5371
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/TabIndexedElement.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element supporting "sequential focus navigation" using the 'tabindex' attribute.
+ *
+ * @abstract
+ */
+class TabIndexedElement extends ElementMixin {
+ /**
+ * Tab index value.
+ *
+ * @var number|null
+ */
+ protected $tabIndex = null;
+
+ public static $targetPropertyName = 'tabIndexed';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param number|null $config['tabIndex'] Tab index value. Use 0 to use default ordering, use -1 to
+ * prevent tab focusing, use null to suppress the `tabindex` attribute. (default: 0)
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['tabIndexed'] ) ? $config['tabIndexed'] : $element;
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->setTabIndex( isset( $config['tabIndex'] ) ? $config['tabIndex'] : 0 );
+ }
+
+ /**
+ * Set tab index value.
+ *
+ * @param number|null $tabIndex Tab index value or null for no tab index
+ * @chainable
+ */
+ public function setTabIndex( $tabIndex ) {
+ $tabIndex = is_numeric( $tabIndex ) ? $tabIndex : null;
+
+ if ( $this->tabIndex !== $tabIndex ) {
+ $this->tabIndex = $tabIndex;
+ $this->updateTabIndex();
+ }
+
+ return $this;
+ }
+
+ /**
+ * Update the tabIndex attribute, in case of changes to tabIndex or disabled
+ * state.
+ *
+ * @chainable
+ */
+ public function updateTabIndex() {
+ $disabled = $this->element->isDisabled();
+ if ( $this->tabIndex !== null ) {
+ $this->target->setAttributes( array(
+ // Do not index over disabled elements
+ 'tabindex' => $disabled ? -1 : $this->tabIndex,
+ // ChromeVox and NVDA do not seem to inherit this from parent elements
+ 'aria-disabled' => ( $disabled ? 'true' : 'false' )
+ ) );
+ } else {
+ $this->target->removeAttributes( array( 'tabindex', 'aria-disabled' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Get tab index value.
+ *
+ * @return number|null Tab index value
+ */
+ public function getTabIndex() {
+ return $this->tabIndex;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->tabIndex !== 0 ) {
+ $config['tabIndex'] = $this->tabIndex;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/elements/TitledElement.php b/vendor/oojs/oojs-ui/php/elements/TitledElement.php
new file mode 100644
index 00000000..5f1317c4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/elements/TitledElement.php
@@ -0,0 +1,74 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Element with a title.
+ *
+ * Titles are rendered by the browser and are made visible when hovering the element. Titles are
+ * not visible on touch devices.
+ *
+ * @abstract
+ */
+class TitledElement extends ElementMixin {
+ /**
+ * Title text.
+ *
+ * @var string
+ */
+ protected $title = null;
+
+ public static $targetPropertyName = 'titled';
+
+ /**
+ * @param Element $element Element being mixed into
+ * @param array $config Configuration options
+ * @param string $config['title'] Title. If not provided, the static property 'title' is used.
+ */
+ public function __construct( Element $element, array $config = array() ) {
+ // Parent constructor
+ $target = isset( $config['titled'] ) ? $config['titled'] : $element;
+ parent::__construct( $element, $target, $config );
+
+ // Initialization
+ $this->setTitle(
+ isset( $config['title'] ) ? $config['title'] :
+ ( isset( $element::$title ) ? $element::$title : null )
+ );
+ }
+
+ /**
+ * Set title.
+ *
+ * @param string|null $title Title text or null for no title
+ * @chainable
+ */
+ public function setTitle( $title ) {
+ if ( $this->title !== $title ) {
+ $this->title = $title;
+ if ( $title !== null ) {
+ $this->target->setAttributes( array( 'title' => $title ) );
+ } else {
+ $this->target->removeAttributes( array( 'title' ) );
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get title.
+ *
+ * @return string Title string
+ */
+ public function getTitle() {
+ return $this->title;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->title !== null ) {
+ $config['title'] = $this->title;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/FieldLayout.php b/vendor/oojs/oojs-ui/php/layouts/FieldLayout.php
new file mode 100644
index 00000000..ef0d4c6c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/FieldLayout.php
@@ -0,0 +1,140 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout made of a field and optional label.
+ *
+ * Available label alignment modes include:
+ * - left: Label is before the field and aligned away from it, best for when the user will be
+ * scanning for a specific label in a form with many fields
+ * - right: Label is before the field and aligned toward it, best for forms the user is very
+ * familiar with and will tab through field checking quickly to verify which field they are in
+ * - top: Label is before the field and above it, best for when the user will need to fill out all
+ * fields from top to bottom in a form with few fields
+ * - inline: Label is after the field and aligned toward it, best for small boolean fields like
+ * checkboxes or radio buttons
+ */
+class FieldLayout extends Layout {
+
+ /**
+ * Alignment.
+ *
+ * @var string
+ */
+ protected $align;
+
+ /**
+ * Field widget to be laid out.
+ *
+ * @var Widget
+ */
+ protected $fieldWidget;
+
+ private $field, $body, $help;
+
+ /**
+ * @param Widget $fieldWidget Field widget
+ * @param array $config Configuration options
+ * @param string $config['align'] Alignment mode, either 'left', 'right', 'top' or 'inline'
+ * (default: 'left')
+ * @param string $config['help'] Explanatory text shown as a '?' icon.
+ */
+ public function __construct( $fieldWidget, array $config = array() ) {
+ // Allow passing positional parameters inside the config array
+ if ( is_array( $fieldWidget ) && isset( $fieldWidget['fieldWidget'] ) ) {
+ $config = $fieldWidget;
+ $fieldWidget = $config['fieldWidget'];
+ }
+
+ $hasInputWidget = $fieldWidget instanceof InputWidget;
+
+ // Config initialization
+ $config = array_merge( array( 'align' => 'left' ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Properties
+ $this->fieldWidget = $fieldWidget;
+ $this->field = new Tag( 'div' );
+ $this->body = new Tag( $hasInputWidget ? 'label' : 'div' );
+ if ( isset( $config['help'] ) ) {
+ $this->help = new ButtonWidget( array(
+ 'classes' => array( 'oo-ui-fieldLayout-help' ),
+ 'framed' => false,
+ 'icon' => 'info',
+ 'title' => $config['help'],
+ ) );
+ } else {
+ $this->help = '';
+ }
+
+ // Mixins
+ $this->mixin( new LabelElement( $this, $config ) );
+
+ // Initialization
+ $this
+ ->addClasses( array( 'oo-ui-fieldLayout' ) )
+ ->appendContent( $this->help, $this->body );
+ $this->body->addClasses( array( 'oo-ui-fieldLayout-body' ) );
+ $this->field
+ ->addClasses( array( 'oo-ui-fieldLayout-field' ) )
+ ->toggleClasses( array( 'oo-ui-fieldLayout-disable' ), $this->fieldWidget->isDisabled() )
+ ->appendContent( $this->fieldWidget );
+
+ $this->setAlignment( $config['align'] );
+ }
+
+ /**
+ * Get the field.
+ *
+ * @return Widget Field widget
+ */
+ public function getField() {
+ return $this->fieldWidget;
+ }
+
+ /**
+ * Set the field alignment mode.
+ *
+ * @param string $value Alignment mode, either 'left', 'right', 'top' or 'inline'
+ * @chainable
+ */
+ protected function setAlignment( $value ) {
+ if ( $value !== $this->align ) {
+ // Default to 'left'
+ if ( !in_array( $value, array( 'left', 'right', 'top', 'inline' ) ) ) {
+ $value = 'left';
+ }
+ // Reorder elements
+ $this->body->clearContent();
+ if ( $value === 'inline' ) {
+ $this->body->appendContent( $this->field, $this->label );
+ } else {
+ $this->body->appendContent( $this->label, $this->field );
+ }
+ // Set classes. The following classes can be used here:
+ // * oo-ui-fieldLayout-align-left
+ // * oo-ui-fieldLayout-align-right
+ // * oo-ui-fieldLayout-align-top
+ // * oo-ui-fieldLayout-align-inline
+ if ( $this->align ) {
+ $this->removeClasses( array( 'oo-ui-fieldLayout-align-' . $this->align ) );
+ }
+ $this->addClasses( array( 'oo-ui-fieldLayout-align-' . $value ) );
+ $this->align = $value;
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $config['fieldWidget'] = $this->fieldWidget;
+ $config['align'] = $this->align;
+ if ( $this->help !== '' ) {
+ $config['help'] = $this->help->getTitle();
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php b/vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php
new file mode 100644
index 00000000..f9faa353
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/FieldsetLayout.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout made of a fieldset and optional legend.
+ *
+ * Just add FieldLayout items.
+ */
+class FieldsetLayout extends Layout {
+ /**
+ * @param array $config Configuration options
+ * @param FieldLayout[] $config['items'] Items to add
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new LabelElement( $this, $config ) );
+ $this->mixin( new GroupElement( $this, $config ) );
+
+ // Initialization
+ $this
+ ->addClasses( array( 'oo-ui-fieldsetLayout' ) )
+ ->prependContent( $this->icon, $this->label, $this->group );
+ if ( isset( $config['items'] ) ) {
+ $this->addItems( $config['items'] );
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/FormLayout.php b/vendor/oojs/oojs-ui/php/layouts/FormLayout.php
new file mode 100644
index 00000000..ebeb89de
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/FormLayout.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout with an HTML form.
+ */
+class FormLayout extends Layout {
+
+ /* Static properties */
+
+ public static $tagName = 'form';
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['method'] HTML form `method` attribute
+ * @param string $config['action'] HTML form `action` attribute
+ * @param string $config['enctype'] HTML form `enctype` attribute
+ * @param FieldsetLayout[] $config['items'] Items to add
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new GroupElement( $this, array_merge( $config, array( 'group' => $this ) ) ) );
+
+ // Initialization
+ $attributeWhitelist = array( 'method', 'action', 'enctype' );
+ $this
+ ->addClasses( array( 'oo-ui-formLayout' ) )
+ ->setAttributes( array_intersect_key( $config, array_flip( $attributeWhitelist ) ) );
+ if ( isset( $config['items'] ) ) {
+ $this->addItems( $config['items'] );
+ }
+ }
+
+ public function getConfig( &$config ) {
+ foreach ( array( 'method', 'action', 'enctype' ) as $attr ) {
+ $value = $this->getAttribute( $attr );
+ if ( $value !== null ) {
+ $config[$attr] = $value;
+ }
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/layouts/PanelLayout.php b/vendor/oojs/oojs-ui/php/layouts/PanelLayout.php
new file mode 100644
index 00000000..64931374
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/layouts/PanelLayout.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Layout that expands to cover the entire area of its parent, with optional scrolling and padding.
+ */
+class PanelLayout extends Layout {
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['scrollable'] Allow vertical scrolling (default: false)
+ * @param boolean $config['padded'] Pad the content from the edges (default: false)
+ * @param boolean $config['expanded'] Expand size to fill the entire parent element
+ * (default: true)
+ * @param boolean $config['framed'] Wrap in a frame to visually separate from outside content
+ * (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Config initialization
+ $config = array_merge( array(
+ 'scrollable' => false,
+ 'padded' => false,
+ 'expanded' => true,
+ 'framed' => false,
+ ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-panelLayout' ) );
+ if ( $config['scrollable'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-scrollable' ) );
+ }
+ if ( $config['padded'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-padded' ) );
+ }
+ if ( $config['expanded'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-expanded' ) );
+ }
+ if ( $config['framed'] ) {
+ $this->addClasses( array( 'oo-ui-panelLayout-framed' ) );
+ }
+ }
+ public function getConfig( &$config ) {
+ if ( $this->hasClass( 'oo-ui-panelLayout-scrollable' ) ) {
+ $config['scrollable'] = true;
+ }
+ if ( $this->hasClass( 'oo-ui-panelLayout-padded' ) ) {
+ $config['padded'] = true;
+ }
+ if ( !$this->hasClass( 'oo-ui-panelLayout-expanded' ) ) {
+ $config['expanded'] = false;
+ }
+ if ( $this->hasClass( 'oo-ui-panelLayout-framed' ) ) {
+ $config['framed'] = true;
+ }
+ $config['content'] = $this->content;
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/themes/ApexTheme.php b/vendor/oojs/oojs-ui/php/themes/ApexTheme.php
new file mode 100644
index 00000000..e812736e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/themes/ApexTheme.php
@@ -0,0 +1,6 @@
+<?php
+
+namespace OOUI;
+
+class ApexTheme extends Theme {
+}
diff --git a/vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php b/vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php
new file mode 100644
index 00000000..86e4c353
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/themes/MediaWikiTheme.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace OOUI;
+
+class MediaWikiTheme extends Theme {
+
+ /* Methods */
+
+ public function getElementClasses( Element $element ) {
+ $variants = array(
+ 'warning' => false,
+ 'invert' => false,
+ 'progressive' => false,
+ 'constructive' => false,
+ 'destructive' => false
+ );
+
+ // Parent method
+ $classes = parent::getElementClasses( $element );
+
+ if ( $element->supports( array( 'hasFlag' ) ) ) {
+ $isFramed = $element->supports( array( 'isFramed' ) ) && $element->isFramed();
+ if ( $isFramed && ( $element->isDisabled() || $element->hasFlag( 'primary' ) ) ) {
+ $variants['invert'] = true;
+ } else {
+ $variants['progressive'] = $element->hasFlag( 'progressive' );
+ $variants['constructive'] = $element->hasFlag( 'constructive' );
+ $variants['destructive'] = $element->hasFlag( 'destructive' );
+ $variants['warning'] = $element->hasFlag( 'warning' );
+ }
+ }
+
+ foreach ( $variants as $variant => $toggle ) {
+ $classes[$toggle ? 'on' : 'off'][] = 'oo-ui-image-' . $variant;
+ }
+
+ return $classes;
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php b/vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php
new file mode 100644
index 00000000..79d3aaa1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/ButtonGroupWidget.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Group widget for multiple related buttons.
+ *
+ * Use together with ButtonWidget.
+ */
+class ButtonGroupWidget extends Widget {
+ /**
+ * @param array $config Configuration options
+ * @param ButtonWidget[] $config['items'] Buttons to add
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new GroupElement( $this, array_merge( $config, array( 'group' => $this ) ) ) );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-buttonGroupWidget' ) );
+ if ( isset( $config['items'] ) ) {
+ $this->addItems( $config['items'] );
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php
new file mode 100644
index 00000000..b3bcb63b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/ButtonInputWidget.php
@@ -0,0 +1,110 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * A button that is an input widget. Intended to be used within a FormLayout.
+ */
+class ButtonInputWidget extends InputWidget {
+ /* Properties */
+
+ /**
+ * Whether to use `<input/>` rather than `<button/>`.
+ *
+ * @var boolean
+ */
+ protected $useInputTag;
+
+ private $labelElementMixin;
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['type'] HTML tag `type` attribute, may be 'button', 'submit' or 'reset'
+ * (default: 'button')
+ * @param boolean $config['useInputTag'] Whether to use `<input/>` rather than `<button/>`. Only
+ * useful if you need IE 6 support in a form with multiple buttons. If you use this option,
+ * icons and indicators will not be displayed, it won't be possible to have a non-plaintext
+ * label, and it won't be possible to set a value (which will internally become identical to the
+ * label). (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Configuration initialization
+ $config = array_merge( array( 'type' => 'button', 'useInputTag' => false ), $config );
+
+ // Properties (must be set before parent constructor, which calls setValue())
+ $this->useInputTag = $config['useInputTag'];
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new ButtonElement( $this,
+ array_merge( $config, array( 'button' => $this->input ) ) ) );
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new IndicatorElement( $this, $config ) );
+ // HACK: We need to have access to the mixin to override the setLabel() method
+ $this->mixin( $this->labelElementMixin = new LabelElement( $this, $config ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this->input ) ) ) );
+
+ // Initialization
+ if ( !$config['useInputTag'] ) {
+ $this->input->appendContent( $this->icon, $this->label, $this->indicator );
+ }
+
+ // HACK: This is done in LabelElement mixin, but doesn't call our overridden method because of
+ // how we implement mixins. Switching to traits will fix that.
+ $this->setLabel( isset( $config['label'] ) ? $config['label'] : null );
+
+ $this->addClasses( array( 'oo-ui-buttonInputWidget' ) );
+ }
+
+ protected function getInputElement( $config ) {
+ $input = new Tag( $config['useInputTag'] ? 'input' : 'button' );
+ $input->setAttributes( array( 'type' => $config['type'] ) );
+ return $input;
+ }
+
+ /**
+ * Set label value.
+ *
+ * Overridden to support setting the 'value' of `<input/>` elements.
+ *
+ * @param string|null $label Label text
+ * @chainable
+ */
+ public function setLabel( $label ) {
+ $this->labelElementMixin->setLabel( $label );
+
+ if ( $this->useInputTag ) {
+ // Discard non-plaintext labels
+ $label = is_string( $label ) ? $label : '';
+ $this->input->setValue( $label );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set the value of the input.
+ *
+ * Overridden to disable for `<input/>` elements, which have value identical to the label.
+ *
+ * @param string $value New value
+ * @chainable
+ */
+ public function setValue( $value ) {
+ if ( !$this->useInputTag ) {
+ parent::setValue( $value );
+ }
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->useInputTag ) {
+ $config['useInputTag'] = true;
+ }
+ $config['type'] = $this->input->getAttribute( 'type' );
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php b/vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php
new file mode 100644
index 00000000..f26608b1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/ButtonWidget.php
@@ -0,0 +1,166 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Generic widget for buttons.
+ */
+class ButtonWidget extends Widget {
+
+ /**
+ * Hyperlink to visit when clicked.
+ *
+ * @var string
+ */
+ protected $href = null;
+
+ /**
+ * Target to open hyperlink in.
+ *
+ * @var string
+ */
+ protected $target = null;
+
+ /**
+ * Search engine traversal hint.
+ *
+ * True if search engines should avoid following this hyperlink.
+ *
+ * @var boolean
+ */
+ protected $noFollow = true;
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['href'] Hyperlink to visit when clicked
+ * @param string $config['target'] Target to open hyperlink in
+ * @param boolean $config['noFollow'] Search engine traversal hint (default: true)
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new ButtonElement( $this, $config ) );
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new IndicatorElement( $this, $config ) );
+ $this->mixin( new LabelElement( $this, $config ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this->button ) ) ) );
+ $this->mixin( new FlaggedElement( $this, $config ) );
+ $this->mixin( new TabIndexedElement( $this,
+ array_merge( $config, array( 'tabIndexed' => $this->button ) ) ) );
+
+ // Initialization
+ $this->button->appendContent( $this->icon, $this->label, $this->indicator );
+ $this
+ ->addClasses( array( 'oo-ui-buttonWidget' ) )
+ ->appendContent( $this->button );
+
+ $this->setHref( isset( $config['href'] ) ? $config['href'] : null );
+ $this->setTarget( isset( $config['target'] ) ? $config['target'] : null );
+ $this->setNoFollow( isset( $config['noFollow'] ) ? $config['noFollow'] : true );
+ }
+
+ /**
+ * Get hyperlink location.
+ *
+ * @return string Hyperlink location
+ */
+ public function getHref() {
+ return $this->href;
+ }
+
+ /**
+ * Get hyperlink target.
+ *
+ * @return string Hyperlink target
+ */
+ public function getTarget() {
+ return $this->target;
+ }
+
+ /**
+ * Get search engine traversal hint.
+ *
+ * @return boolean Whether search engines should avoid traversing this hyperlink
+ */
+ public function getNoFollow() {
+ return $this->noFollow;
+ }
+
+ /**
+ * Set hyperlink location.
+ *
+ * @param string|null $href Hyperlink location, null to remove
+ */
+ public function setHref( $href ) {
+ $this->href = is_string( $href ) ? $href : null;
+
+ $this->updateHref();
+
+ return $this;
+ }
+
+ /**
+ * Update the href attribute, in case of changes to href or disabled
+ * state.
+ *
+ * @chainable
+ */
+ public function updateHref() {
+ if ( $this->href !== null && !$this->isDisabled() ) {
+ $this->button->setAttributes( array( 'href' => $this->href ) );
+ } else {
+ $this->button->removeAttributes( array( 'href' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Set hyperlink target.
+ *
+ * @param string|null $target Hyperlink target, null to remove
+ */
+ public function setTarget( $target ) {
+ $this->target = is_string( $target ) ? $target : null;
+
+ if ( $this->target !== null ) {
+ $this->button->setAttributes( array( 'target' => $target ) );
+ } else {
+ $this->button->removeAttributes( array( 'target' ) );
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set search engine traversal hint.
+ *
+ * @param boolean $noFollow True if search engines should avoid traversing this hyperlink
+ */
+ public function setNoFollow( $noFollow ) {
+ $this->noFollow = is_bool( $noFollow ) ? $noFollow : true;
+
+ if ( $this->noFollow ) {
+ $this->button->setAttributes( array( 'rel' => 'nofollow' ) );
+ } else {
+ $this->button->removeAttributes( array( 'rel' ) );
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->href !== null ) {
+ $config['href'] = $this->href;
+ }
+ if ( $this->target !== null ) {
+ $config['target'] = $this->target;
+ }
+ if ( $this->noFollow !== true ) {
+ $config['noFollow'] = $this->noFollow;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php
new file mode 100644
index 00000000..bda09c66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/CheckboxInputWidget.php
@@ -0,0 +1,70 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Checkbox input widget.
+ */
+class CheckboxInputWidget extends InputWidget {
+
+ /* Properties */
+
+ /**
+ * Whether the checkbox is selected.
+ *
+ * @var boolean
+ */
+ protected $selected;
+
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['selected'] Whether the checkbox is initially selected
+ * (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-checkboxInputWidget' ) );
+ $this->setSelected( isset( $config['selected'] ) ? $config['selected'] : false );
+ }
+
+ protected function getInputElement( $config ) {
+ $input = new Tag( 'input' );
+ $input->setAttributes( array( 'type' => 'checkbox' ) );
+ return $input;
+ }
+
+ /**
+ * Set selection state of this checkbox.
+ *
+ * @param boolean $state Whether the checkbox is selected
+ * @chainable
+ */
+ public function setSelected( $state ) {
+ $this->selected = (bool)$state;
+ if ( $this->selected ) {
+ $this->input->setAttributes( array( 'checked' => 'checked' ) );
+ } else {
+ $this->input->removeAttributes( array( 'checked' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Check if this checkbox is selected.
+ *
+ * @return boolean Checkbox is selected
+ */
+ public function isSelected() {
+ return $this->selected;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->selected ) {
+ $config['selected'] = $this->selected;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php
new file mode 100644
index 00000000..ae541a66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/DropdownInputWidget.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Dropdown input widget, wrapping a `<select>` element. Intended to be used within a
+ * OO.ui.FormLayout.
+ */
+class DropdownInputWidget extends InputWidget {
+
+ /**
+ * HTML `<option>` tags for this widget.
+ * @var Tag[]
+ */
+ protected $options = array();
+
+ /**
+ * @param array $config Configuration options
+ * @param array[] $config['options'] Array of menu options in the format
+ * `array( 'data' => …, 'label' => … )`
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->setOptions( isset( $config['options'] ) ? $config['options'] : array() );
+ $this->addClasses( array( 'oo-ui-dropdownInputWidget' ) );
+ }
+
+ protected function getInputElement( $config ) {
+ return new Tag( 'select' );
+ }
+
+ public function setValue( $value ) {
+ $this->value = $this->cleanUpValue( $value );
+ foreach ( $this->options as &$opt ) {
+ if ( $opt->getAttribute( 'value' ) === $this->value ) {
+ $opt->setAttributes( array( 'selected' => 'selected' ) );
+ } else {
+ $opt->removeAttributes( array( 'selected' ) );
+ }
+ }
+ return $this;
+ }
+
+
+ /**
+ * Set the options available for this input.
+ *
+ * @param array[] $options Array of menu options in the format
+ * `array( 'data' => …, 'label' => … )`
+ * @chainable
+ */
+ public function setOptions( $options ) {
+ $value = $this->getValue();
+ $isValueAvailable = false;
+ $this->options = array();
+
+ // Rebuild the dropdown menu
+ $this->input->clearContent();
+ foreach ( $options as $opt ) {
+ $option = new Tag( 'option' );
+ $option->setAttributes( array( 'value' => $opt['data'] ) );
+ $option->appendContent( isset( $opt['label'] ) ? $opt['label'] : $opt['data'] );
+
+ if ( $value === $opt['data'] ) {
+ $isValueAvailable = true;
+ }
+
+ $this->options[] = $option;
+ $this->input->appendContent( $option );
+ }
+
+ // Restore the previous value, or reset to something sensible
+ if ( $isValueAvailable ) {
+ // Previous value is still available
+ $this->setValue( $value );
+ } else {
+ // No longer valid, reset
+ if ( count( $options ) ) {
+ $this->setValue( $options[0]['data'] );
+ }
+ }
+
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $o = array();
+ foreach ( $this->options as $option ) {
+ $label = $option->content[0];
+ $data = $option->getAttribute( 'value' );
+ $o[] = array( 'data' => $data, 'label' => $label );
+ }
+ $config['options'] = $o;
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/IconWidget.php b/vendor/oojs/oojs-ui/php/widgets/IconWidget.php
new file mode 100644
index 00000000..f8273f37
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/IconWidget.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Icon widget.
+ *
+ * See IconElement for more information.
+ */
+class IconWidget extends Widget {
+
+ /* Static properties */
+
+ public static $tagName = 'span';
+
+ /**
+ * @param array $config Configuration options
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new IconElement( $this,
+ array_merge( $config, array( 'iconElement' => $this ) ) ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this ) ) ) );
+ $this->mixin( new FlaggedElement( $this,
+ array_merge( $config, array( 'flagged' => $this ) ) ) );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-iconWidget' ) );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php b/vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php
new file mode 100644
index 00000000..01f2055d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/IndicatorWidget.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Indicator widget.
+ *
+ * See IndicatorElement for more information.
+ */
+class IndicatorWidget extends Widget {
+
+ /* Static properties */
+
+ public static $tagName = 'span';
+
+ /**
+ * @param array $config Configuration options
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new IndicatorElement( $this,
+ array_merge( $config, array( 'indicatorElement' => $this ) ) ) );
+ $this->mixin( new TitledElement( $this,
+ array_merge( $config, array( 'titled' => $this ) ) ) );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-indicatorWidget' ) );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/InputWidget.php b/vendor/oojs/oojs-ui/php/widgets/InputWidget.php
new file mode 100644
index 00000000..234d3145
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/InputWidget.php
@@ -0,0 +1,144 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Base class for input widgets.
+ *
+ * @abstract
+ */
+class InputWidget extends Widget {
+
+ /* Properties */
+
+ /**
+ * Input element.
+ *
+ * @var Tag
+ */
+ protected $input;
+
+ /**
+ * Input value.
+ *
+ * @var string
+ */
+ protected $value = '';
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['name'] HTML input name (default: '')
+ * @param string $config['value'] Input value (default: '')
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Properties
+ $this->input = $this->getInputElement( $config );
+
+ // Mixins
+ $this->mixin( new FlaggedElement( $this,
+ array_merge( $config, array( 'flagged' => $this ) ) ) );
+ $this->mixin( new TabIndexedElement( $this,
+ array_merge( $config, array( 'tabIndexed' => $this->input ) ) ) );
+
+ // Initialization
+ if ( isset( $config['name'] ) ) {
+ $this->input->setAttributes( array( 'name' => $config['name'] ) );
+ }
+ if ( $this->isDisabled() ) {
+ $this->input->setAttributes( array( 'disabled' => 'disabled' ) );
+ }
+ $this
+ ->addClasses( array( 'oo-ui-inputWidget' ) )
+ ->appendContent( $this->input );
+ $this->appendContent( new Tag( 'span' ) );
+ $this->setValue( isset( $config['value'] ) ? $config['value'] : null );
+ }
+
+ /**
+ * Get input element.
+ *
+ * @param array $config Configuration options
+ * @return Tag Input element
+ */
+ protected function getInputElement( $config ) {
+ return new Tag( 'input' );
+ }
+
+ /**
+ * Get the value of the input.
+ *
+ * @return string Input value
+ */
+ public function getValue() {
+ return $this->value;
+ }
+
+ /**
+ * Sets the direction of the current input, either RTL or LTR
+ *
+ * @param boolean $isRTL
+ */
+ public function setRTL( $isRTL ) {
+ if ( $isRTL ) {
+ $this->input->removeClasses( array( 'oo-ui-ltr' ) );
+ $this->input->addClasses( array( 'oo-ui-rtl' ) );
+ } else {
+ $this->input->removeClasses( array( 'oo-ui-rtl' ) );
+ $this->input->addClasses( array( 'oo-ui-ltr' ) );
+ }
+ }
+
+ /**
+ * Set the value of the input.
+ *
+ * @param string $value New value
+ * @chainable
+ */
+ public function setValue( $value ) {
+ $this->value = $this->cleanUpValue( $value );
+ $this->input->setValue( $this->value );
+ return $this;
+ }
+
+ /**
+ * Clean up incoming value.
+ *
+ * Ensures value is a string, and converts null to empty string.
+ *
+ * @param string $value Original value
+ * @return string Cleaned up value
+ */
+ protected function cleanUpValue( $value ) {
+ if ( $value === null ) {
+ return '';
+ } else {
+ return (string)$value;
+ }
+ }
+
+ public function setDisabled( $state ) {
+ parent::setDisabled( $state );
+ if ( isset( $this->input ) ) {
+ if ( $this->isDisabled() ) {
+ $this->input->setAttributes( array( 'disabled' => 'disabled' ) );
+ } else {
+ $this->input->removeAttributes( array( 'disabled' ) );
+ }
+ }
+ return $this;
+ }
+
+ public function getConfig( &$config ) {
+ $name = $this->input->getAttribute( 'name' );
+ if ( $name !== null ) {
+ $config['name'] = $name;
+ }
+ if ( $this->value !== '' ) {
+ $config['value'] = $this->value;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/LabelWidget.php b/vendor/oojs/oojs-ui/php/widgets/LabelWidget.php
new file mode 100644
index 00000000..b59a5f25
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/LabelWidget.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Label widget.
+ */
+class LabelWidget extends Widget {
+
+ /* Static properties */
+
+ public static $tagName = 'span';
+
+ /* Properties */
+
+ /**
+ * Associated input element.
+ *
+ * @var InputWidget|null
+ */
+ protected $input;
+
+ /**
+ * @param array $config Configuration options
+ * @param InputWidget $config['input'] Input widget this label is for
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Mixins
+ $this->mixin( new LabelElement( $this,
+ array_merge( $config, array( 'labelElement' => $this ) ) ) );
+
+ // Properties
+ $this->input = isset( $config['input'] ) ? $config['input'] : null;
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-labelWidget' ) );
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->input !== null ) {
+ $config['input'] = $this->input;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php
new file mode 100644
index 00000000..26da29d0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/RadioInputWidget.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Radio input widget.
+ */
+class RadioInputWidget extends InputWidget {
+
+ /**
+ * @param array $config Configuration options
+ * @param boolean $config['selected'] Whether the radio button is initially selected
+ * (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Initialization
+ $this->addClasses( array( 'oo-ui-radioInputWidget' ) );
+ $this->setSelected( isset( $config['selected'] ) ? $config['selected'] : false );
+ }
+
+ protected function getInputElement( $config ) {
+ $input = new Tag( 'input' );
+ $input->setAttributes( array( 'type' => 'radio' ) );
+ return $input;
+ }
+
+ /**
+ * Set selection state of this radio button.
+ *
+ * @param boolean $state Whether the button is selected
+ */
+ public function setSelected( $state ) {
+ // RadioInputWidget doesn't track its state.
+ if ( $state ) {
+ $this->input->setAttributes( array( 'checked' => 'checked' ) );
+ } else {
+ $this->input->removeAttributes( array( 'checked' ) );
+ }
+ return $this;
+ }
+
+ /**
+ * Check if this radio button is selected.
+ *
+ * @return boolean Radio is selected
+ */
+ public function isSelected() {
+ return $this->input->getAttribute( 'checked' ) === 'checked';
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->isSelected() ) {
+ $config['selected'] = true;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php b/vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php
new file mode 100644
index 00000000..a5f31f74
--- /dev/null
+++ b/vendor/oojs/oojs-ui/php/widgets/TextInputWidget.php
@@ -0,0 +1,151 @@
+<?php
+
+namespace OOUI;
+
+/**
+ * Input widget with a text field.
+ */
+class TextInputWidget extends InputWidget {
+
+ /* Properties */
+
+ /**
+ * Prevent changes.
+ *
+ * @var boolean
+ */
+ protected $readOnly = false;
+
+ /**
+ * Allow multiple lines of text.
+ *
+ * @var boolean
+ */
+ protected $multiline = false;
+
+ /**
+ * @param array $config Configuration options
+ * @param string $config['type'] HTML tag `type` attribute (default: 'text')
+ * @param string $config['placeholder'] Placeholder text
+ * @param boolean $config['autofocus'] Ask the browser to focus this widget, using the 'autofocus'
+ * HTML attribute (default: false)
+ * @param boolean $config['readOnly'] Prevent changes (default: false)
+ * @param number $config['maxLength'] Maximum allowed number of characters to input
+ * @param boolean $config['multiline'] Allow multiple lines of text (default: false)
+ * @param boolean $config['required'] Mark the field as required (default: false)
+ */
+ public function __construct( array $config = array() ) {
+ // Config initialization
+ $config = array_merge( array(
+ 'readOnly' => false,
+ 'autofocus' => false,
+ 'required' => false,
+ ), $config );
+
+ // Parent constructor
+ parent::__construct( $config );
+
+ // Properties
+ $this->multiline = isset( $config['multiline'] ) ? (bool)$config['multiline'] : false;
+
+ // Mixins
+ $this->mixin( new IconElement( $this, $config ) );
+ $this->mixin( new IndicatorElement( $this, $config ) );
+
+ // Initialization
+ $this
+ ->addClasses( array( 'oo-ui-textInputWidget' ) )
+ ->appendContent( $this->icon, $this->indicator );
+ $this->setReadOnly( $config['readOnly'] );
+ if ( isset( $config['placeholder'] ) ) {
+ $this->input->setAttributes( array( 'placeholder' => $config['placeholder'] ) );
+ }
+ if ( isset( $config['maxLength'] ) ) {
+ $this->input->setAttributes( array( 'maxlength' => $config['maxLength'] ) );
+ }
+ if ( $config['autofocus'] ) {
+ $this->input->setAttributes( array( 'autofocus' => 'autofocus' ) );
+ }
+ if ( $config['required'] ) {
+ $this->input->setAttributes( array( 'required' => 'required', 'aria-required' => 'true' ) );
+ }
+ }
+
+ /**
+ * Check if the widget is read-only.
+ *
+ * @return boolean
+ */
+ public function isReadOnly() {
+ return $this->readOnly;
+ }
+
+ /**
+ * Set the read-only state of the widget. This should probably change the widget's appearance and
+ * prevent it from being used.
+ *
+ * @param boolean $state Make input read-only
+ * @chainable
+ */
+ public function setReadOnly( $state ) {
+ $this->readOnly = (bool)$state;
+ if ( $this->readOnly ) {
+ $this->input->setAttributes( array( 'readonly' => 'readonly' ) );
+ } else {
+ $this->input->removeAttributes( array( 'readonly' ) );
+ }
+ return $this;
+ }
+
+ protected function getInputElement( $config ) {
+ if ( isset( $config['multiline'] ) && $config['multiline'] ) {
+ return new Tag( 'textarea' );
+ } else {
+ $type = isset( $config['type'] ) ? $config['type'] : 'text';
+ $input = new Tag( 'input' );
+ $input->setAttributes( array( 'type' => $type ) );
+ return $input;
+ }
+ }
+
+ /**
+ * Check if input supports multiple lines.
+ *
+ * @return boolean
+ */
+ public function isMultiline() {
+ return (bool)$this->multiline;
+ }
+
+ public function getConfig( &$config ) {
+ if ( $this->isMultiline() ) {
+ $config['multiline'] = true;
+ } else {
+ $type = $this->input->getAttribute( 'type' );
+ if ( $type !== 'text' ) {
+ $config['type'] = $type;
+ }
+ }
+ if ( $this->isReadOnly() ) {
+ $config['readOnly'] = true;
+ }
+ $placeholder = $this->input->getAttribute( 'placeholder' );
+ if ( $placeholder !== null ) {
+ $config['placeholder'] = $placeholder;
+ }
+ $maxlength = $this->input->getAttribute( 'maxlength' );
+ if ( $maxlength !== null ) {
+ $config['maxLength'] = $maxlength;
+ }
+ $autofocus = $this->input->getAttribute( 'autofocus' );
+ if ( $autofocus !== null ) {
+ $config['autofocus'] = true;
+ }
+ $required = $this->input->getAttribute( 'required' );
+ $ariarequired = $this->input->getAttribute( 'aria-required' );
+ if ( ( $required !== null ) || ( $ariarequired !== null ) ) {
+ $config['required'] = true;
+ }
+ return parent::getConfig( $config );
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/ActionSet.js b/vendor/oojs/oojs-ui/src/ActionSet.js
new file mode 100644
index 00000000..875ca605
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/ActionSet.js
@@ -0,0 +1,504 @@
+/**
+ * ActionSets manage the behavior of the {@link OO.ui.ActionWidget action widgets} that comprise them.
+ * Actions can be made available for specific contexts (modes) and circumstances
+ * (abilities). Action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
+ *
+ * ActionSets contain two types of actions:
+ *
+ * - Special: Special actions are the first visible actions with special flags, such as 'safe' and 'primary', the default special flags. Additional special flags can be configured in subclasses with the static #specialFlags property.
+ * - Other: Other actions include all non-special visible actions.
+ *
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
+ *
+ * @example
+ * // Example: An action set used in a process dialog
+ * function MyProcessDialog( config ) {
+ * MyProcessDialog.super.call( this, config );
+ * }
+ * OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
+ * MyProcessDialog.static.title = 'An action set in a process dialog';
+ * // An action set that uses modes ('edit' and 'help' mode, in this example).
+ * MyProcessDialog.static.actions = [
+ * { action: 'continue', modes: 'edit', label: 'Continue', flags: [ 'primary', 'constructive' ] },
+ * { action: 'help', modes: 'edit', label: 'Help' },
+ * { modes: 'edit', label: 'Cancel', flags: 'safe' },
+ * { action: 'back', modes: 'help', label: 'Back', flags: 'safe' }
+ * ];
+ *
+ * MyProcessDialog.prototype.initialize = function () {
+ * MyProcessDialog.super.prototype.initialize.apply( this, arguments );
+ * this.panel1 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ * this.panel1.$element.append( '<p>This dialog uses an action set (continue, help, cancel, back) configured with modes. This is edit mode. Click \'help\' to see help mode.</p>' );
+ * this.panel2 = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ * this.panel2.$element.append( '<p>This is help mode. Only the \'back\' action widget is configured to be visible here. Click \'back\' to return to \'edit\' mode.</p>' );
+ * this.stackLayout = new OO.ui.StackLayout( {
+ * items: [ this.panel1, this.panel2 ]
+ * } );
+ * this.$body.append( this.stackLayout.$element );
+ * };
+ * MyProcessDialog.prototype.getSetupProcess = function ( data ) {
+ * return MyProcessDialog.super.prototype.getSetupProcess.call( this, data )
+ * .next( function () {
+ * this.actions.setMode( 'edit' );
+ * }, this );
+ * };
+ * MyProcessDialog.prototype.getActionProcess = function ( action ) {
+ * if ( action === 'help' ) {
+ * this.actions.setMode( 'help' );
+ * this.stackLayout.setItem( this.panel2 );
+ * } else if ( action === 'back' ) {
+ * this.actions.setMode( 'edit' );
+ * this.stackLayout.setItem( this.panel1 );
+ * } else if ( action === 'continue' ) {
+ * var dialog = this;
+ * return new OO.ui.Process( function () {
+ * dialog.close();
+ * } );
+ * }
+ * return MyProcessDialog.super.prototype.getActionProcess.call( this, action );
+ * };
+ * MyProcessDialog.prototype.getBodyHeight = function () {
+ * return this.panel1.$element.outerHeight( true );
+ * };
+ * var windowManager = new OO.ui.WindowManager();
+ * $( 'body' ).append( windowManager.$element );
+ * var dialog = new MyProcessDialog( {
+ * size: 'medium'
+ * } );
+ * windowManager.addWindows( [ dialog ] );
+ * windowManager.openWindow( dialog );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
+ *
+ * @abstract
+ * @class
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.ActionSet = function OoUiActionSet( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Mixin constructors
+ OO.EventEmitter.call( this );
+
+ // Properties
+ this.list = [];
+ this.categories = {
+ actions: 'getAction',
+ flags: 'getFlags',
+ modes: 'getModes'
+ };
+ this.categorized = {};
+ this.special = {};
+ this.others = [];
+ this.organized = false;
+ this.changing = false;
+ this.changed = false;
+};
+
+/* Setup */
+
+OO.mixinClass( OO.ui.ActionSet, OO.EventEmitter );
+
+/* Static Properties */
+
+/**
+ * Symbolic name of the flags used to identify special actions. Special actions are displayed in the
+ * header of a {@link OO.ui.ProcessDialog process dialog}.
+ * See the [OOjs UI documentation on MediaWiki][2] for more information and examples.
+ *
+ * [2]:https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.ActionSet.static.specialFlags = [ 'safe', 'primary' ];
+
+/* Events */
+
+/**
+ * @event click
+ *
+ * A 'click' event is emitted when an action is clicked.
+ *
+ * @param {OO.ui.ActionWidget} action Action that was clicked
+ */
+
+/**
+ * @event resize
+ *
+ * A 'resize' event is emitted when an action widget is resized.
+ *
+ * @param {OO.ui.ActionWidget} action Action that was resized
+ */
+
+/**
+ * @event add
+ *
+ * An 'add' event is emitted when actions are {@link #method-add added} to the action set.
+ *
+ * @param {OO.ui.ActionWidget[]} added Actions added
+ */
+
+/**
+ * @event remove
+ *
+ * A 'remove' event is emitted when actions are {@link #method-remove removed}
+ * or {@link #clear cleared}.
+ *
+ * @param {OO.ui.ActionWidget[]} added Actions removed
+ */
+
+/**
+ * @event change
+ *
+ * A 'change' event is emitted when actions are {@link #method-add added}, {@link #clear cleared},
+ * or {@link #method-remove removed} from the action set or when the {@link #setMode mode} is changed.
+ *
+ */
+
+/* Methods */
+
+/**
+ * Handle action change events.
+ *
+ * @private
+ * @fires change
+ */
+OO.ui.ActionSet.prototype.onActionChange = function () {
+ this.organized = false;
+ if ( this.changing ) {
+ this.changed = true;
+ } else {
+ this.emit( 'change' );
+ }
+};
+
+/**
+ * Check if an action is one of the special actions.
+ *
+ * @param {OO.ui.ActionWidget} action Action to check
+ * @return {boolean} Action is special
+ */
+OO.ui.ActionSet.prototype.isSpecial = function ( action ) {
+ var flag;
+
+ for ( flag in this.special ) {
+ if ( action === this.special[ flag ] ) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Get action widgets based on the specified filter: ‘actions’, ‘flags’, ‘modes’, ‘visible’,
+ * or ‘disabled’.
+ *
+ * @param {Object} [filters] Filters to use, omit to get all actions
+ * @param {string|string[]} [filters.actions] Actions that action widgets must have
+ * @param {string|string[]} [filters.flags] Flags that action widgets must have (e.g., 'safe')
+ * @param {string|string[]} [filters.modes] Modes that action widgets must have
+ * @param {boolean} [filters.visible] Action widgets must be visible
+ * @param {boolean} [filters.disabled] Action widgets must be disabled
+ * @return {OO.ui.ActionWidget[]} Action widgets matching all criteria
+ */
+OO.ui.ActionSet.prototype.get = function ( filters ) {
+ var i, len, list, category, actions, index, match, matches;
+
+ if ( filters ) {
+ this.organize();
+
+ // Collect category candidates
+ matches = [];
+ for ( category in this.categorized ) {
+ list = filters[ category ];
+ if ( list ) {
+ if ( !Array.isArray( list ) ) {
+ list = [ list ];
+ }
+ for ( i = 0, len = list.length; i < len; i++ ) {
+ actions = this.categorized[ category ][ list[ i ] ];
+ if ( Array.isArray( actions ) ) {
+ matches.push.apply( matches, actions );
+ }
+ }
+ }
+ }
+ // Remove by boolean filters
+ for ( i = 0, len = matches.length; i < len; i++ ) {
+ match = matches[ i ];
+ if (
+ ( filters.visible !== undefined && match.isVisible() !== filters.visible ) ||
+ ( filters.disabled !== undefined && match.isDisabled() !== filters.disabled )
+ ) {
+ matches.splice( i, 1 );
+ len--;
+ i--;
+ }
+ }
+ // Remove duplicates
+ for ( i = 0, len = matches.length; i < len; i++ ) {
+ match = matches[ i ];
+ index = matches.lastIndexOf( match );
+ while ( index !== i ) {
+ matches.splice( index, 1 );
+ len--;
+ index = matches.lastIndexOf( match );
+ }
+ }
+ return matches;
+ }
+ return this.list.slice();
+};
+
+/**
+ * Get 'special' actions.
+ *
+ * Special actions are the first visible action widgets with special flags, such as 'safe' and 'primary'.
+ * Special flags can be configured in subclasses by changing the static #specialFlags property.
+ *
+ * @return {OO.ui.ActionWidget[]|null} 'Special' action widgets.
+ */
+OO.ui.ActionSet.prototype.getSpecial = function () {
+ this.organize();
+ return $.extend( {}, this.special );
+};
+
+/**
+ * Get 'other' actions.
+ *
+ * Other actions include all non-special visible action widgets.
+ *
+ * @return {OO.ui.ActionWidget[]} 'Other' action widgets
+ */
+OO.ui.ActionSet.prototype.getOthers = function () {
+ this.organize();
+ return this.others.slice();
+};
+
+/**
+ * Set the mode (e.g., ‘edit’ or ‘view’). Only {@link OO.ui.ActionWidget#modes actions} configured
+ * to be available in the specified mode will be made visible. All other actions will be hidden.
+ *
+ * @param {string} mode The mode. Only actions configured to be available in the specified
+ * mode will be made visible.
+ * @chainable
+ * @fires toggle
+ * @fires change
+ */
+OO.ui.ActionSet.prototype.setMode = function ( mode ) {
+ var i, len, action;
+
+ this.changing = true;
+ for ( i = 0, len = this.list.length; i < len; i++ ) {
+ action = this.list[ i ];
+ action.toggle( action.hasMode( mode ) );
+ }
+
+ this.organized = false;
+ this.changing = false;
+ this.emit( 'change' );
+
+ return this;
+};
+
+/**
+ * Set the abilities of the specified actions.
+ *
+ * Action widgets that are configured with the specified actions will be enabled
+ * or disabled based on the boolean values specified in the `actions`
+ * parameter.
+ *
+ * @param {Object.<string,boolean>} actions A list keyed by action name with boolean
+ * values that indicate whether or not the action should be enabled.
+ * @chainable
+ */
+OO.ui.ActionSet.prototype.setAbilities = function ( actions ) {
+ var i, len, action, item;
+
+ for ( i = 0, len = this.list.length; i < len; i++ ) {
+ item = this.list[ i ];
+ action = item.getAction();
+ if ( actions[ action ] !== undefined ) {
+ item.setDisabled( !actions[ action ] );
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Executes a function once per action.
+ *
+ * When making changes to multiple actions, use this method instead of iterating over the actions
+ * manually to defer emitting a #change event until after all actions have been changed.
+ *
+ * @param {Object|null} actions Filters to use to determine which actions to iterate over; see #get
+ * @param {Function} callback Callback to run for each action; callback is invoked with three
+ * arguments: the action, the action's index, the list of actions being iterated over
+ * @chainable
+ */
+OO.ui.ActionSet.prototype.forEach = function ( filter, callback ) {
+ this.changed = false;
+ this.changing = true;
+ this.get( filter ).forEach( callback );
+ this.changing = false;
+ if ( this.changed ) {
+ this.emit( 'change' );
+ }
+
+ return this;
+};
+
+/**
+ * Add action widgets to the action set.
+ *
+ * @param {OO.ui.ActionWidget[]} actions Action widgets to add
+ * @chainable
+ * @fires add
+ * @fires change
+ */
+OO.ui.ActionSet.prototype.add = function ( actions ) {
+ var i, len, action;
+
+ this.changing = true;
+ for ( i = 0, len = actions.length; i < len; i++ ) {
+ action = actions[ i ];
+ action.connect( this, {
+ click: [ 'emit', 'click', action ],
+ resize: [ 'emit', 'resize', action ],
+ toggle: [ 'onActionChange' ]
+ } );
+ this.list.push( action );
+ }
+ this.organized = false;
+ this.emit( 'add', actions );
+ this.changing = false;
+ this.emit( 'change' );
+
+ return this;
+};
+
+/**
+ * Remove action widgets from the set.
+ *
+ * To remove all actions, you may wish to use the #clear method instead.
+ *
+ * @param {OO.ui.ActionWidget[]} actions Action widgets to remove
+ * @chainable
+ * @fires remove
+ * @fires change
+ */
+OO.ui.ActionSet.prototype.remove = function ( actions ) {
+ var i, len, index, action;
+
+ this.changing = true;
+ for ( i = 0, len = actions.length; i < len; i++ ) {
+ action = actions[ i ];
+ index = this.list.indexOf( action );
+ if ( index !== -1 ) {
+ action.disconnect( this );
+ this.list.splice( index, 1 );
+ }
+ }
+ this.organized = false;
+ this.emit( 'remove', actions );
+ this.changing = false;
+ this.emit( 'change' );
+
+ return this;
+};
+
+/**
+ * Remove all action widets from the set.
+ *
+ * To remove only specified actions, use the {@link #method-remove remove} method instead.
+ *
+ * @chainable
+ * @fires remove
+ * @fires change
+ */
+OO.ui.ActionSet.prototype.clear = function () {
+ var i, len, action,
+ removed = this.list.slice();
+
+ this.changing = true;
+ for ( i = 0, len = this.list.length; i < len; i++ ) {
+ action = this.list[ i ];
+ action.disconnect( this );
+ }
+
+ this.list = [];
+
+ this.organized = false;
+ this.emit( 'remove', removed );
+ this.changing = false;
+ this.emit( 'change' );
+
+ return this;
+};
+
+/**
+ * Organize actions.
+ *
+ * This is called whenever organized information is requested. It will only reorganize the actions
+ * if something has changed since the last time it ran.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.ActionSet.prototype.organize = function () {
+ var i, iLen, j, jLen, flag, action, category, list, item, special,
+ specialFlags = this.constructor.static.specialFlags;
+
+ if ( !this.organized ) {
+ this.categorized = {};
+ this.special = {};
+ this.others = [];
+ for ( i = 0, iLen = this.list.length; i < iLen; i++ ) {
+ action = this.list[ i ];
+ if ( action.isVisible() ) {
+ // Populate categories
+ for ( category in this.categories ) {
+ if ( !this.categorized[ category ] ) {
+ this.categorized[ category ] = {};
+ }
+ list = action[ this.categories[ category ] ]();
+ if ( !Array.isArray( list ) ) {
+ list = [ list ];
+ }
+ for ( j = 0, jLen = list.length; j < jLen; j++ ) {
+ item = list[ j ];
+ if ( !this.categorized[ category ][ item ] ) {
+ this.categorized[ category ][ item ] = [];
+ }
+ this.categorized[ category ][ item ].push( action );
+ }
+ }
+ // Populate special/others
+ special = false;
+ for ( j = 0, jLen = specialFlags.length; j < jLen; j++ ) {
+ flag = specialFlags[ j ];
+ if ( !this.special[ flag ] && action.hasFlag( flag ) ) {
+ this.special[ flag ] = action;
+ special = true;
+ break;
+ }
+ }
+ if ( !special ) {
+ this.others.push( action );
+ }
+ }
+ }
+ this.organized = true;
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/Dialog.js b/vendor/oojs/oojs-ui/src/Dialog.js
new file mode 100644
index 00000000..33226038
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Dialog.js
@@ -0,0 +1,323 @@
+/**
+ * The Dialog class serves as the base class for the other types of dialogs.
+ * Unless extended to include controls, the rendered dialog box is a simple window
+ * that users can close by hitting the ‘Esc’ key. Dialog windows are used with OO.ui.WindowManager,
+ * which opens, closes, and controls the presentation of the window. See the
+ * [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * @example
+ * // A simple dialog window.
+ * function MyDialog( config ) {
+ * MyDialog.super.call( this, config );
+ * }
+ * OO.inheritClass( MyDialog, OO.ui.Dialog );
+ * MyDialog.prototype.initialize = function () {
+ * MyDialog.super.prototype.initialize.call( this );
+ * this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ * this.content.$element.append( '<p>A simple dialog window. Press \'Esc\' to close.</p>' );
+ * this.$body.append( this.content.$element );
+ * };
+ * MyDialog.prototype.getBodyHeight = function () {
+ * return this.content.$element.outerHeight( true );
+ * };
+ * var myDialog = new MyDialog( {
+ * size: 'medium'
+ * } );
+ * // Create and append a window manager, which opens and closes the window.
+ * var windowManager = new OO.ui.WindowManager();
+ * $( 'body' ).append( windowManager.$element );
+ * windowManager.addWindows( [ myDialog ] );
+ * // Open the window!
+ * windowManager.openWindow( myDialog );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Dialogs
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Window
+ * @mixins OO.ui.PendingElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.Dialog = function OoUiDialog( config ) {
+ // Parent constructor
+ OO.ui.Dialog.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.PendingElement.call( this );
+
+ // Properties
+ this.actions = new OO.ui.ActionSet();
+ this.attachedActions = [];
+ this.currentAction = null;
+ this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this );
+
+ // Events
+ this.actions.connect( this, {
+ click: 'onActionClick',
+ resize: 'onActionResize',
+ change: 'onActionsChange'
+ } );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-dialog' )
+ .attr( 'role', 'dialog' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.Dialog, OO.ui.Window );
+OO.mixinClass( OO.ui.Dialog, OO.ui.PendingElement );
+
+/* Static Properties */
+
+/**
+ * Symbolic name of dialog.
+ *
+ * The dialog class must have a symbolic name in order to be registered with OO.Factory.
+ * Please see the [OOjs UI documentation on MediaWiki] [3] for more information.
+ *
+ * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.Dialog.static.name = '';
+
+/**
+ * The dialog title.
+ *
+ * The title can be specified as a plaintext string, a {@link OO.ui.LabelElement Label} node, or a function
+ * that will produce a Label node or string. The title can also be specified with data passed to the
+ * constructor (see #getSetupProcess). In this case, the static value will be overriden.
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {jQuery|string|Function}
+ */
+OO.ui.Dialog.static.title = '';
+
+/**
+ * An array of configured {@link OO.ui.ActionWidget action widgets}.
+ *
+ * Actions can also be specified with data passed to the constructor (see #getSetupProcess). In this case, the static
+ * value will be overriden.
+ *
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
+ *
+ * @static
+ * @inheritable
+ * @property {Object[]}
+ */
+OO.ui.Dialog.static.actions = [];
+
+/**
+ * Close the dialog when the 'Esc' key is pressed.
+ *
+ * @static
+ * @abstract
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.Dialog.static.escapable = true;
+
+/* Methods */
+
+/**
+ * Handle frame document key down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.Dialog.prototype.onDocumentKeyDown = function ( e ) {
+ if ( e.which === OO.ui.Keys.ESCAPE ) {
+ this.close();
+ e.preventDefault();
+ e.stopPropagation();
+ }
+};
+
+/**
+ * Handle action resized events.
+ *
+ * @private
+ * @param {OO.ui.ActionWidget} action Action that was resized
+ */
+OO.ui.Dialog.prototype.onActionResize = function () {
+ // Override in subclass
+};
+
+/**
+ * Handle action click events.
+ *
+ * @private
+ * @param {OO.ui.ActionWidget} action Action that was clicked
+ */
+OO.ui.Dialog.prototype.onActionClick = function ( action ) {
+ if ( !this.isPending() ) {
+ this.executeAction( action.getAction() );
+ }
+};
+
+/**
+ * Handle actions change event.
+ *
+ * @private
+ */
+OO.ui.Dialog.prototype.onActionsChange = function () {
+ this.detachActions();
+ if ( !this.isClosing() ) {
+ this.attachActions();
+ }
+};
+
+/**
+ * Get the set of actions used by the dialog.
+ *
+ * @return {OO.ui.ActionSet}
+ */
+OO.ui.Dialog.prototype.getActions = function () {
+ return this.actions;
+};
+
+/**
+ * Get a process for taking action.
+ *
+ * When you override this method, you can create a new OO.ui.Process and return it, or add additional
+ * accept steps to the process the parent method provides using the {@link OO.ui.Process#first 'first'}
+ * and {@link OO.ui.Process#next 'next'} methods of OO.ui.Process.
+ *
+ * @abstract
+ * @param {string} [action] Symbolic name of action
+ * @return {OO.ui.Process} Action process
+ */
+OO.ui.Dialog.prototype.getActionProcess = function ( action ) {
+ return new OO.ui.Process()
+ .next( function () {
+ if ( !action ) {
+ // An empty action always closes the dialog without data, which should always be
+ // safe and make no changes
+ this.close();
+ }
+ }, this );
+};
+
+/**
+ * @inheritdoc
+ *
+ * @param {Object} [data] Dialog opening data
+ * @param {jQuery|string|Function|null} [data.title] Dialog title, omit to use
+ * the {@link #static-title static title}
+ * @param {Object[]} [data.actions] List of configuration options for each
+ * {@link OO.ui.ActionWidget action widget}, omit to use {@link #static-actions static actions}.
+ */
+OO.ui.Dialog.prototype.getSetupProcess = function ( data ) {
+ data = data || {};
+
+ // Parent method
+ return OO.ui.Dialog.super.prototype.getSetupProcess.call( this, data )
+ .next( function () {
+ var config = this.constructor.static,
+ actions = data.actions !== undefined ? data.actions : config.actions;
+
+ this.title.setLabel(
+ data.title !== undefined ? data.title : this.constructor.static.title
+ );
+ this.actions.add( this.getActionWidgets( actions ) );
+
+ if ( this.constructor.static.escapable ) {
+ this.$document.on( 'keydown', this.onDocumentKeyDownHandler );
+ }
+ }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.Dialog.prototype.getTeardownProcess = function ( data ) {
+ // Parent method
+ return OO.ui.Dialog.super.prototype.getTeardownProcess.call( this, data )
+ .first( function () {
+ if ( this.constructor.static.escapable ) {
+ this.$document.off( 'keydown', this.onDocumentKeyDownHandler );
+ }
+
+ this.actions.clear();
+ this.currentAction = null;
+ }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.Dialog.prototype.initialize = function () {
+ // Parent method
+ OO.ui.Dialog.super.prototype.initialize.call( this );
+
+ // Properties
+ this.title = new OO.ui.LabelWidget();
+
+ // Initialization
+ this.$content.addClass( 'oo-ui-dialog-content' );
+ this.setPendingElement( this.$head );
+};
+
+/**
+ * Get action widgets from a list of configs
+ *
+ * @param {Object[]} actions Action widget configs
+ * @return {OO.ui.ActionWidget[]} Action widgets
+ */
+OO.ui.Dialog.prototype.getActionWidgets = function ( actions ) {
+ var i, len, widgets = [];
+ for ( i = 0, len = actions.length; i < len; i++ ) {
+ widgets.push(
+ new OO.ui.ActionWidget( actions[ i ] )
+ );
+ }
+ return widgets;
+};
+
+/**
+ * Attach action actions.
+ *
+ * @protected
+ */
+OO.ui.Dialog.prototype.attachActions = function () {
+ // Remember the list of potentially attached actions
+ this.attachedActions = this.actions.get();
+};
+
+/**
+ * Detach action actions.
+ *
+ * @protected
+ * @chainable
+ */
+OO.ui.Dialog.prototype.detachActions = function () {
+ var i, len;
+
+ // Detach all actions that may have been previously attached
+ for ( i = 0, len = this.attachedActions.length; i < len; i++ ) {
+ this.attachedActions[ i ].$element.detach();
+ }
+ this.attachedActions = [];
+};
+
+/**
+ * Execute an action.
+ *
+ * @param {string} action Symbolic name of action to execute
+ * @return {jQuery.Promise} Promise resolved when action completes, rejected if it fails
+ */
+OO.ui.Dialog.prototype.executeAction = function ( action ) {
+ this.pushPending();
+ this.currentAction = action;
+ return this.getActionProcess( action ).execute()
+ .always( this.popPending.bind( this ) );
+};
diff --git a/vendor/oojs/oojs-ui/src/Element.js b/vendor/oojs/oojs-ui/src/Element.js
new file mode 100644
index 00000000..127eb503
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Element.js
@@ -0,0 +1,748 @@
+/**
+ * Each Element represents a rendering in the DOM—a button or an icon, for example, or anything
+ * that is visible to a user. Unlike {@link OO.ui.Widget widgets}, plain elements usually do not have events
+ * connected to them and can't be interacted with.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string[]} [classes] The names of the CSS classes to apply to the element. CSS styles are added
+ * to the top level (e.g., the outermost div) of the element. See the [OOjs UI documentation on MediaWiki][2]
+ * for an example.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#cssExample
+ * @cfg {string} [id] The HTML id attribute used in the rendered tag.
+ * @cfg {string} [text] Text to insert
+ * @cfg {Array} [content] An array of content elements to append (after #text).
+ * Strings will be html-escaped; use an OO.ui.HtmlSnippet to append raw HTML.
+ * Instances of OO.ui.Element will have their $element appended.
+ * @cfg {jQuery} [$content] Content elements to append (after #text)
+ * @cfg {Mixed} [data] Custom data of any type or combination of types (e.g., string, number, array, object).
+ * Data can also be specified with the #setData method.
+ */
+OO.ui.Element = function OoUiElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$ = $;
+ this.visible = true;
+ this.data = config.data;
+ this.$element = config.$element ||
+ $( document.createElement( this.getTagName() ) );
+ this.elementGroup = null;
+ this.debouncedUpdateThemeClassesHandler = this.debouncedUpdateThemeClasses.bind( this );
+ this.updateThemeClassesPending = false;
+
+ // Initialization
+ if ( Array.isArray( config.classes ) ) {
+ this.$element.addClass( config.classes.join( ' ' ) );
+ }
+ if ( config.id ) {
+ this.$element.attr( 'id', config.id );
+ }
+ if ( config.text ) {
+ this.$element.text( config.text );
+ }
+ if ( config.content ) {
+ // The `content` property treats plain strings as text; use an
+ // HtmlSnippet to append HTML content. `OO.ui.Element`s get their
+ // appropriate $element appended.
+ this.$element.append( config.content.map( function ( v ) {
+ if ( typeof v === 'string' ) {
+ // Escape string so it is properly represented in HTML.
+ return document.createTextNode( v );
+ } else if ( v instanceof OO.ui.HtmlSnippet ) {
+ // Bypass escaping.
+ return v.toString();
+ } else if ( v instanceof OO.ui.Element ) {
+ return v.$element;
+ }
+ return v;
+ } ) );
+ }
+ if ( config.$content ) {
+ // The `$content` property treats plain strings as HTML.
+ this.$element.append( config.$content );
+ }
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.Element );
+
+/* Static Properties */
+
+/**
+ * The name of the HTML tag used by the element.
+ *
+ * The static value may be ignored if the #getTagName method is overridden.
+ *
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.Element.static.tagName = 'div';
+
+/* Static Methods */
+
+/**
+ * Reconstitute a JavaScript object corresponding to a widget created
+ * by the PHP implementation.
+ *
+ * @param {string|HTMLElement|jQuery} idOrNode
+ * A DOM id (if a string) or node for the widget to infuse.
+ * @return {OO.ui.Element}
+ * The `OO.ui.Element` corresponding to this (infusable) document node.
+ * For `Tag` objects emitted on the HTML side (used occasionally for content)
+ * the value returned is a newly-created Element wrapping around the existing
+ * DOM node.
+ */
+OO.ui.Element.static.infuse = function ( idOrNode ) {
+ var obj = OO.ui.Element.static.unsafeInfuse( idOrNode, true );
+ // Verify that the type matches up.
+ // FIXME: uncomment after T89721 is fixed (see T90929)
+ /*
+ if ( !( obj instanceof this['class'] ) ) {
+ throw new Error( 'Infusion type mismatch!' );
+ }
+ */
+ return obj;
+};
+
+/**
+ * Implementation helper for `infuse`; skips the type check and has an
+ * extra property so that only the top-level invocation touches the DOM.
+ * @private
+ * @param {string|HTMLElement|jQuery} idOrNode
+ * @param {boolean} top True only for top-level invocation.
+ * @return {OO.ui.Element}
+ */
+OO.ui.Element.static.unsafeInfuse = function ( idOrNode, top ) {
+ // look for a cached result of a previous infusion.
+ var id, $elem, data, cls, obj;
+ if ( typeof idOrNode === 'string' ) {
+ id = idOrNode;
+ $elem = $( document.getElementById( id ) );
+ } else {
+ $elem = $( idOrNode );
+ id = $elem.attr( 'id' );
+ }
+ data = $elem.data( 'ooui-infused' );
+ if ( data ) {
+ // cached!
+ if ( data === true ) {
+ throw new Error( 'Circular dependency! ' + id );
+ }
+ return data;
+ }
+ if ( !$elem.length ) {
+ throw new Error( 'Widget not found: ' + id );
+ }
+ data = $elem.attr( 'data-ooui' );
+ if ( !data ) {
+ throw new Error( 'No infusion data found: ' + id );
+ }
+ try {
+ data = $.parseJSON( data );
+ } catch ( _ ) {
+ data = null;
+ }
+ if ( !( data && data._ ) ) {
+ throw new Error( 'No valid infusion data found: ' + id );
+ }
+ if ( data._ === 'Tag' ) {
+ // Special case: this is a raw Tag; wrap existing node, don't rebuild.
+ return new OO.ui.Element( { $element: $elem } );
+ }
+ cls = OO.ui[data._];
+ if ( !cls ) {
+ throw new Error( 'Unknown widget type: ' + id );
+ }
+ $elem.data( 'ooui-infused', true ); // prevent loops
+ data.id = id; // implicit
+ data = OO.copy( data, null, function deserialize( value ) {
+ if ( OO.isPlainObject( value ) ) {
+ if ( value.tag ) {
+ return OO.ui.Element.static.unsafeInfuse( value.tag, false );
+ }
+ if ( value.html ) {
+ return new OO.ui.HtmlSnippet( value.html );
+ }
+ }
+ } );
+ // jscs:disable requireCapitalizedConstructors
+ obj = new cls( data ); // rebuild widget
+ // now replace old DOM with this new DOM.
+ if ( top ) {
+ $elem.replaceWith( obj.$element );
+ }
+ obj.$element.data( 'ooui-infused', obj );
+ // set the 'data-ooui' attribute so we can identify infused widgets
+ obj.$element.attr( 'data-ooui', '' );
+ return obj;
+};
+
+/**
+ * Get a jQuery function within a specific document.
+ *
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} context Context to bind the function to
+ * @param {jQuery} [$iframe] HTML iframe element that contains the document, omit if document is
+ * not in an iframe
+ * @return {Function} Bound jQuery function
+ */
+OO.ui.Element.static.getJQuery = function ( context, $iframe ) {
+ function wrapper( selector ) {
+ return $( selector, wrapper.context );
+ }
+
+ wrapper.context = this.getDocument( context );
+
+ if ( $iframe ) {
+ wrapper.$iframe = $iframe;
+ }
+
+ return wrapper;
+};
+
+/**
+ * Get the document of an element.
+ *
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Object to get the document for
+ * @return {HTMLDocument|null} Document object
+ */
+OO.ui.Element.static.getDocument = function ( obj ) {
+ // jQuery - selections created "offscreen" won't have a context, so .context isn't reliable
+ return ( obj[ 0 ] && obj[ 0 ].ownerDocument ) ||
+ // Empty jQuery selections might have a context
+ obj.context ||
+ // HTMLElement
+ obj.ownerDocument ||
+ // Window
+ obj.document ||
+ // HTMLDocument
+ ( obj.nodeType === 9 && obj ) ||
+ null;
+};
+
+/**
+ * Get the window of an element or document.
+ *
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the window for
+ * @return {Window} Window object
+ */
+OO.ui.Element.static.getWindow = function ( obj ) {
+ var doc = this.getDocument( obj );
+ return doc.parentWindow || doc.defaultView;
+};
+
+/**
+ * Get the direction of an element or document.
+ *
+ * @static
+ * @param {jQuery|HTMLElement|HTMLDocument|Window} obj Context to get the direction for
+ * @return {string} Text direction, either 'ltr' or 'rtl'
+ */
+OO.ui.Element.static.getDir = function ( obj ) {
+ var isDoc, isWin;
+
+ if ( obj instanceof jQuery ) {
+ obj = obj[ 0 ];
+ }
+ isDoc = obj.nodeType === 9;
+ isWin = obj.document !== undefined;
+ if ( isDoc || isWin ) {
+ if ( isWin ) {
+ obj = obj.document;
+ }
+ obj = obj.body;
+ }
+ return $( obj ).css( 'direction' );
+};
+
+/**
+ * Get the offset between two frames.
+ *
+ * TODO: Make this function not use recursion.
+ *
+ * @static
+ * @param {Window} from Window of the child frame
+ * @param {Window} [to=window] Window of the parent frame
+ * @param {Object} [offset] Offset to start with, used internally
+ * @return {Object} Offset object, containing left and top properties
+ */
+OO.ui.Element.static.getFrameOffset = function ( from, to, offset ) {
+ var i, len, frames, frame, rect;
+
+ if ( !to ) {
+ to = window;
+ }
+ if ( !offset ) {
+ offset = { top: 0, left: 0 };
+ }
+ if ( from.parent === from ) {
+ return offset;
+ }
+
+ // Get iframe element
+ frames = from.parent.document.getElementsByTagName( 'iframe' );
+ for ( i = 0, len = frames.length; i < len; i++ ) {
+ if ( frames[ i ].contentWindow === from ) {
+ frame = frames[ i ];
+ break;
+ }
+ }
+
+ // Recursively accumulate offset values
+ if ( frame ) {
+ rect = frame.getBoundingClientRect();
+ offset.left += rect.left;
+ offset.top += rect.top;
+ if ( from !== to ) {
+ this.getFrameOffset( from.parent, offset );
+ }
+ }
+ return offset;
+};
+
+/**
+ * Get the offset between two elements.
+ *
+ * The two elements may be in a different frame, but in that case the frame $element is in must
+ * be contained in the frame $anchor is in.
+ *
+ * @static
+ * @param {jQuery} $element Element whose position to get
+ * @param {jQuery} $anchor Element to get $element's position relative to
+ * @return {Object} Translated position coordinates, containing top and left properties
+ */
+OO.ui.Element.static.getRelativePosition = function ( $element, $anchor ) {
+ var iframe, iframePos,
+ pos = $element.offset(),
+ anchorPos = $anchor.offset(),
+ elementDocument = this.getDocument( $element ),
+ anchorDocument = this.getDocument( $anchor );
+
+ // If $element isn't in the same document as $anchor, traverse up
+ while ( elementDocument !== anchorDocument ) {
+ iframe = elementDocument.defaultView.frameElement;
+ if ( !iframe ) {
+ throw new Error( '$element frame is not contained in $anchor frame' );
+ }
+ iframePos = $( iframe ).offset();
+ pos.left += iframePos.left;
+ pos.top += iframePos.top;
+ elementDocument = iframe.ownerDocument;
+ }
+ pos.left -= anchorPos.left;
+ pos.top -= anchorPos.top;
+ return pos;
+};
+
+/**
+ * Get element border sizes.
+ *
+ * @static
+ * @param {HTMLElement} el Element to measure
+ * @return {Object} Dimensions object with `top`, `left`, `bottom` and `right` properties
+ */
+OO.ui.Element.static.getBorders = function ( el ) {
+ var doc = el.ownerDocument,
+ win = doc.parentWindow || doc.defaultView,
+ style = win && win.getComputedStyle ?
+ win.getComputedStyle( el, null ) :
+ el.currentStyle,
+ $el = $( el ),
+ top = parseFloat( style ? style.borderTopWidth : $el.css( 'borderTopWidth' ) ) || 0,
+ left = parseFloat( style ? style.borderLeftWidth : $el.css( 'borderLeftWidth' ) ) || 0,
+ bottom = parseFloat( style ? style.borderBottomWidth : $el.css( 'borderBottomWidth' ) ) || 0,
+ right = parseFloat( style ? style.borderRightWidth : $el.css( 'borderRightWidth' ) ) || 0;
+
+ return {
+ top: top,
+ left: left,
+ bottom: bottom,
+ right: right
+ };
+};
+
+/**
+ * Get dimensions of an element or window.
+ *
+ * @static
+ * @param {HTMLElement|Window} el Element to measure
+ * @return {Object} Dimensions object with `borders`, `scroll`, `scrollbar` and `rect` properties
+ */
+OO.ui.Element.static.getDimensions = function ( el ) {
+ var $el, $win,
+ doc = el.ownerDocument || el.document,
+ win = doc.parentWindow || doc.defaultView;
+
+ if ( win === el || el === doc.documentElement ) {
+ $win = $( win );
+ return {
+ borders: { top: 0, left: 0, bottom: 0, right: 0 },
+ scroll: {
+ top: $win.scrollTop(),
+ left: $win.scrollLeft()
+ },
+ scrollbar: { right: 0, bottom: 0 },
+ rect: {
+ top: 0,
+ left: 0,
+ bottom: $win.innerHeight(),
+ right: $win.innerWidth()
+ }
+ };
+ } else {
+ $el = $( el );
+ return {
+ borders: this.getBorders( el ),
+ scroll: {
+ top: $el.scrollTop(),
+ left: $el.scrollLeft()
+ },
+ scrollbar: {
+ right: $el.innerWidth() - el.clientWidth,
+ bottom: $el.innerHeight() - el.clientHeight
+ },
+ rect: el.getBoundingClientRect()
+ };
+ }
+};
+
+/**
+ * Get scrollable object parent
+ *
+ * documentElement can't be used to get or set the scrollTop
+ * property on Blink. Changing and testing its value lets us
+ * use 'body' or 'documentElement' based on what is working.
+ *
+ * https://code.google.com/p/chromium/issues/detail?id=303131
+ *
+ * @static
+ * @param {HTMLElement} el Element to find scrollable parent for
+ * @return {HTMLElement} Scrollable parent
+ */
+OO.ui.Element.static.getRootScrollableElement = function ( el ) {
+ var scrollTop, body;
+
+ if ( OO.ui.scrollableElement === undefined ) {
+ body = el.ownerDocument.body;
+ scrollTop = body.scrollTop;
+ body.scrollTop = 1;
+
+ if ( body.scrollTop === 1 ) {
+ body.scrollTop = scrollTop;
+ OO.ui.scrollableElement = 'body';
+ } else {
+ OO.ui.scrollableElement = 'documentElement';
+ }
+ }
+
+ return el.ownerDocument[ OO.ui.scrollableElement ];
+};
+
+/**
+ * Get closest scrollable container.
+ *
+ * Traverses up until either a scrollable element or the root is reached, in which case the window
+ * will be returned.
+ *
+ * @static
+ * @param {HTMLElement} el Element to find scrollable container for
+ * @param {string} [dimension] Dimension of scrolling to look for; `x`, `y` or omit for either
+ * @return {HTMLElement} Closest scrollable container
+ */
+OO.ui.Element.static.getClosestScrollableContainer = function ( el, dimension ) {
+ var i, val,
+ props = [ 'overflow' ],
+ $parent = $( el ).parent();
+
+ if ( dimension === 'x' || dimension === 'y' ) {
+ props.push( 'overflow-' + dimension );
+ }
+
+ while ( $parent.length ) {
+ if ( $parent[ 0 ] === this.getRootScrollableElement( el ) ) {
+ return $parent[ 0 ];
+ }
+ i = props.length;
+ while ( i-- ) {
+ val = $parent.css( props[ i ] );
+ if ( val === 'auto' || val === 'scroll' ) {
+ return $parent[ 0 ];
+ }
+ }
+ $parent = $parent.parent();
+ }
+ return this.getDocument( el ).body;
+};
+
+/**
+ * Scroll element into view.
+ *
+ * @static
+ * @param {HTMLElement} el Element to scroll into view
+ * @param {Object} [config] Configuration options
+ * @param {string} [config.duration] jQuery animation duration value
+ * @param {string} [config.direction] Scroll in only one direction, e.g. 'x' or 'y', omit
+ * to scroll in both directions
+ * @param {Function} [config.complete] Function to call when scrolling completes
+ */
+OO.ui.Element.static.scrollIntoView = function ( el, config ) {
+ // Configuration initialization
+ config = config || {};
+
+ var rel, anim = {},
+ callback = typeof config.complete === 'function' && config.complete,
+ sc = this.getClosestScrollableContainer( el, config.direction ),
+ $sc = $( sc ),
+ eld = this.getDimensions( el ),
+ scd = this.getDimensions( sc ),
+ $win = $( this.getWindow( el ) );
+
+ // Compute the distances between the edges of el and the edges of the scroll viewport
+ if ( $sc.is( 'html, body' ) ) {
+ // If the scrollable container is the root, this is easy
+ rel = {
+ top: eld.rect.top,
+ bottom: $win.innerHeight() - eld.rect.bottom,
+ left: eld.rect.left,
+ right: $win.innerWidth() - eld.rect.right
+ };
+ } else {
+ // Otherwise, we have to subtract el's coordinates from sc's coordinates
+ rel = {
+ top: eld.rect.top - ( scd.rect.top + scd.borders.top ),
+ bottom: scd.rect.bottom - scd.borders.bottom - scd.scrollbar.bottom - eld.rect.bottom,
+ left: eld.rect.left - ( scd.rect.left + scd.borders.left ),
+ right: scd.rect.right - scd.borders.right - scd.scrollbar.right - eld.rect.right
+ };
+ }
+
+ if ( !config.direction || config.direction === 'y' ) {
+ if ( rel.top < 0 ) {
+ anim.scrollTop = scd.scroll.top + rel.top;
+ } else if ( rel.top > 0 && rel.bottom < 0 ) {
+ anim.scrollTop = scd.scroll.top + Math.min( rel.top, -rel.bottom );
+ }
+ }
+ if ( !config.direction || config.direction === 'x' ) {
+ if ( rel.left < 0 ) {
+ anim.scrollLeft = scd.scroll.left + rel.left;
+ } else if ( rel.left > 0 && rel.right < 0 ) {
+ anim.scrollLeft = scd.scroll.left + Math.min( rel.left, -rel.right );
+ }
+ }
+ if ( !$.isEmptyObject( anim ) ) {
+ $sc.stop( true ).animate( anim, config.duration || 'fast' );
+ if ( callback ) {
+ $sc.queue( function ( next ) {
+ callback();
+ next();
+ } );
+ }
+ } else {
+ if ( callback ) {
+ callback();
+ }
+ }
+};
+
+/**
+ * Force the browser to reconsider whether it really needs to render scrollbars inside the element
+ * and reserve space for them, because it probably doesn't.
+ *
+ * Workaround primarily for <https://code.google.com/p/chromium/issues/detail?id=387290>, but also
+ * similar bugs in other browsers. "Just" forcing a reflow is not sufficient in all cases, we need
+ * to first actually detach (or hide, but detaching is simpler) all children, *then* force a reflow,
+ * and then reattach (or show) them back.
+ *
+ * @static
+ * @param {HTMLElement} el Element to reconsider the scrollbars on
+ */
+OO.ui.Element.static.reconsiderScrollbars = function ( el ) {
+ var i, len, nodes = [];
+ // Detach all children
+ while ( el.firstChild ) {
+ nodes.push( el.firstChild );
+ el.removeChild( el.firstChild );
+ }
+ // Force reflow
+ void el.offsetHeight;
+ // Reattach all children
+ for ( i = 0, len = nodes.length; i < len; i++ ) {
+ el.appendChild( nodes[ i ] );
+ }
+};
+
+/* Methods */
+
+/**
+ * Toggle visibility of an element.
+ *
+ * @param {boolean} [show] Make element visible, omit to toggle visibility
+ * @fires visible
+ * @chainable
+ */
+OO.ui.Element.prototype.toggle = function ( show ) {
+ show = show === undefined ? !this.visible : !!show;
+
+ if ( show !== this.isVisible() ) {
+ this.visible = show;
+ this.$element.toggleClass( 'oo-ui-element-hidden', !this.visible );
+ this.emit( 'toggle', show );
+ }
+
+ return this;
+};
+
+/**
+ * Check if element is visible.
+ *
+ * @return {boolean} element is visible
+ */
+OO.ui.Element.prototype.isVisible = function () {
+ return this.visible;
+};
+
+/**
+ * Get element data.
+ *
+ * @return {Mixed} Element data
+ */
+OO.ui.Element.prototype.getData = function () {
+ return this.data;
+};
+
+/**
+ * Set element data.
+ *
+ * @param {Mixed} Element data
+ * @chainable
+ */
+OO.ui.Element.prototype.setData = function ( data ) {
+ this.data = data;
+ return this;
+};
+
+/**
+ * Check if element supports one or more methods.
+ *
+ * @param {string|string[]} methods Method or list of methods to check
+ * @return {boolean} All methods are supported
+ */
+OO.ui.Element.prototype.supports = function ( methods ) {
+ var i, len,
+ support = 0;
+
+ methods = Array.isArray( methods ) ? methods : [ methods ];
+ for ( i = 0, len = methods.length; i < len; i++ ) {
+ if ( $.isFunction( this[ methods[ i ] ] ) ) {
+ support++;
+ }
+ }
+
+ return methods.length === support;
+};
+
+/**
+ * Update the theme-provided classes.
+ *
+ * @localdoc This is called in element mixins and widget classes any time state changes.
+ * Updating is debounced, minimizing overhead of changing multiple attributes and
+ * guaranteeing that theme updates do not occur within an element's constructor
+ */
+OO.ui.Element.prototype.updateThemeClasses = function () {
+ if ( !this.updateThemeClassesPending ) {
+ this.updateThemeClassesPending = true;
+ setTimeout( this.debouncedUpdateThemeClassesHandler );
+ }
+};
+
+/**
+ * @private
+ */
+OO.ui.Element.prototype.debouncedUpdateThemeClasses = function () {
+ OO.ui.theme.updateElementClasses( this );
+ this.updateThemeClassesPending = false;
+};
+
+/**
+ * Get the HTML tag name.
+ *
+ * Override this method to base the result on instance information.
+ *
+ * @return {string} HTML tag name
+ */
+OO.ui.Element.prototype.getTagName = function () {
+ return this.constructor.static.tagName;
+};
+
+/**
+ * Check if the element is attached to the DOM
+ * @return {boolean} The element is attached to the DOM
+ */
+OO.ui.Element.prototype.isElementAttached = function () {
+ return $.contains( this.getElementDocument(), this.$element[ 0 ] );
+};
+
+/**
+ * Get the DOM document.
+ *
+ * @return {HTMLDocument} Document object
+ */
+OO.ui.Element.prototype.getElementDocument = function () {
+ // Don't cache this in other ways either because subclasses could can change this.$element
+ return OO.ui.Element.static.getDocument( this.$element );
+};
+
+/**
+ * Get the DOM window.
+ *
+ * @return {Window} Window object
+ */
+OO.ui.Element.prototype.getElementWindow = function () {
+ return OO.ui.Element.static.getWindow( this.$element );
+};
+
+/**
+ * Get closest scrollable container.
+ */
+OO.ui.Element.prototype.getClosestScrollableElementContainer = function () {
+ return OO.ui.Element.static.getClosestScrollableContainer( this.$element[ 0 ] );
+};
+
+/**
+ * Get group element is in.
+ *
+ * @return {OO.ui.GroupElement|null} Group element, null if none
+ */
+OO.ui.Element.prototype.getElementGroup = function () {
+ return this.elementGroup;
+};
+
+/**
+ * Set group element is in.
+ *
+ * @param {OO.ui.GroupElement|null} group Group element, null if none
+ * @chainable
+ */
+OO.ui.Element.prototype.setElementGroup = function ( group ) {
+ this.elementGroup = group;
+ return this;
+};
+
+/**
+ * Scroll element into view.
+ *
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.Element.prototype.scrollElementIntoView = function ( config ) {
+ return OO.ui.Element.static.scrollIntoView( this.$element[ 0 ], config );
+};
diff --git a/vendor/oojs/oojs-ui/src/Error.js b/vendor/oojs/oojs-ui/src/Error.js
new file mode 100644
index 00000000..af87b722
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Error.js
@@ -0,0 +1,91 @@
+/**
+ * Errors contain a required message (either a string or jQuery selection) that is used to describe what went wrong
+ * in a {@link OO.ui.Process process}. The error's #recoverable and #warning configurations are used to customize the
+ * appearance and functionality of the error interface.
+ *
+ * The basic error interface contains a formatted error message as well as two buttons: 'Dismiss' and 'Try again' (i.e., the error
+ * is 'recoverable' by default). If the error is not recoverable, the 'Try again' button will not be rendered and the widget
+ * that initiated the failed process will be disabled.
+ *
+ * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button, which will try the
+ * process again.
+ *
+ * For an example of error interfaces, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Processes_and_errors
+ *
+ * @class
+ *
+ * @constructor
+ * @param {string|jQuery} message Description of error
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [recoverable=true] Error is recoverable.
+ * By default, errors are recoverable, and users can try the process again.
+ * @cfg {boolean} [warning=false] Error is a warning.
+ * If the error is a warning, the error interface will include a
+ * 'Dismiss' and a 'Continue' button. It is the responsibility of the developer to ensure that the warning
+ * is not triggered a second time if the user chooses to continue.
+ */
+OO.ui.Error = function OoUiError( message, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( message ) && config === undefined ) {
+ config = message;
+ message = config.message;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.message = message instanceof jQuery ? message : String( message );
+ this.recoverable = config.recoverable === undefined || !!config.recoverable;
+ this.warning = !!config.warning;
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.Error );
+
+/* Methods */
+
+/**
+ * Check if the error is recoverable.
+ *
+ * If the error is recoverable, users are able to try the process again.
+ *
+ * @return {boolean} Error is recoverable
+ */
+OO.ui.Error.prototype.isRecoverable = function () {
+ return this.recoverable;
+};
+
+/**
+ * Check if the error is a warning.
+ *
+ * If the error is a warning, the error interface will include a 'Dismiss' and a 'Continue' button.
+ *
+ * @return {boolean} Error is warning
+ */
+OO.ui.Error.prototype.isWarning = function () {
+ return this.warning;
+};
+
+/**
+ * Get error message as DOM nodes.
+ *
+ * @return {jQuery} Error message in DOM nodes
+ */
+OO.ui.Error.prototype.getMessage = function () {
+ return this.message instanceof jQuery ?
+ this.message.clone() :
+ $( '<div>' ).text( this.message ).contents();
+};
+
+/**
+ * Get the error message text.
+ *
+ * @return {string} Error message
+ */
+OO.ui.Error.prototype.getMessageText = function () {
+ return this.message instanceof jQuery ? this.message.text() : this.message;
+};
diff --git a/vendor/oojs/oojs-ui/src/HtmlSnippet.js b/vendor/oojs/oojs-ui/src/HtmlSnippet.js
new file mode 100644
index 00000000..b9c5c314
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/HtmlSnippet.js
@@ -0,0 +1,29 @@
+/**
+ * Wraps an HTML snippet for use with configuration values which default
+ * to strings. This bypasses the default html-escaping done to string
+ * values.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {string} [content] HTML content
+ */
+OO.ui.HtmlSnippet = function OoUiHtmlSnippet( content ) {
+ // Properties
+ this.content = content;
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.HtmlSnippet );
+
+/* Methods */
+
+/**
+ * Render into HTML.
+ *
+ * @return {string} Unchanged HTML snippet.
+ */
+OO.ui.HtmlSnippet.prototype.toString = function () {
+ return this.content;
+};
diff --git a/vendor/oojs/oojs-ui/src/Layout.js b/vendor/oojs/oojs-ui/src/Layout.js
new file mode 100644
index 00000000..d0c368f5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Layout.js
@@ -0,0 +1,33 @@
+/**
+ * Layouts are containers for elements and are used to arrange other widgets of arbitrary type in a way
+ * that is centrally controlled and can be updated dynamically. Layouts can be, and usually are, combined.
+ * See {@link OO.ui.FieldsetLayout FieldsetLayout}, {@link OO.ui.FieldLayout FieldLayout}, {@link OO.ui.FormLayout FormLayout},
+ * {@link OO.ui.PanelLayout PanelLayout}, {@link OO.ui.StackLayout StackLayout}, {@link OO.ui.PageLayout PageLayout},
+ * and {@link OO.ui.BookletLayout BookletLayout} for more information and examples.
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.Layout = function OoUiLayout( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.Layout.super.call( this, config );
+
+ // Mixin constructors
+ OO.EventEmitter.call( this );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-layout' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.Layout, OO.ui.Element );
+OO.mixinClass( OO.ui.Layout, OO.EventEmitter );
diff --git a/vendor/oojs/oojs-ui/src/Process.js b/vendor/oojs/oojs-ui/src/Process.js
new file mode 100644
index 00000000..649ffb99
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Process.js
@@ -0,0 +1,166 @@
+/**
+ * A Process is a list of steps that are called in sequence. The step can be a number, a jQuery promise,
+ * or a function:
+ *
+ * - **number**: the process will wait for the specified number of milliseconds before proceeding.
+ * - **promise**: the process will continue to the next step when the promise is successfully resolved
+ * or stop if the promise is rejected.
+ * - **function**: the process will execute the function. The process will stop if the function returns
+ * either a boolean `false` or a promise that is rejected; if the function returns a number, the process
+ * will wait for that number of milliseconds before proceeding.
+ *
+ * If the process fails, an {@link OO.ui.Error error} is generated. Depending on how the error is
+ * configured, users can dismiss the error and try the process again, or not. If a process is stopped,
+ * its remaining steps will not be performed.
+ *
+ * @class
+ *
+ * @constructor
+ * @param {number|jQuery.Promise|Function} step Number of miliseconds to wait before proceeding, promise
+ * that must be resolved before proceeding, or a function to execute. See #createStep for more information. see #createStep for more information
+ * @param {Object} [context=null] Execution context of the function. The context is ignored if the step is
+ * a number or promise.
+ * @return {Object} Step object, with `callback` and `context` properties
+ */
+OO.ui.Process = function ( step, context ) {
+ // Properties
+ this.steps = [];
+
+ // Initialization
+ if ( step !== undefined ) {
+ this.next( step, context );
+ }
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.Process );
+
+/* Methods */
+
+/**
+ * Start the process.
+ *
+ * @return {jQuery.Promise} Promise that is resolved when all steps have successfully completed.
+ * If any of the steps return a promise that is rejected or a boolean false, this promise is rejected
+ * and any remaining steps are not performed.
+ */
+OO.ui.Process.prototype.execute = function () {
+ var i, len, promise;
+
+ /**
+ * Continue execution.
+ *
+ * @ignore
+ * @param {Array} step A function and the context it should be called in
+ * @return {Function} Function that continues the process
+ */
+ function proceed( step ) {
+ return function () {
+ // Execute step in the correct context
+ var deferred,
+ result = step.callback.call( step.context );
+
+ if ( result === false ) {
+ // Use rejected promise for boolean false results
+ return $.Deferred().reject( [] ).promise();
+ }
+ if ( typeof result === 'number' ) {
+ if ( result < 0 ) {
+ throw new Error( 'Cannot go back in time: flux capacitor is out of service' );
+ }
+ // Use a delayed promise for numbers, expecting them to be in milliseconds
+ deferred = $.Deferred();
+ setTimeout( deferred.resolve, result );
+ return deferred.promise();
+ }
+ if ( result instanceof OO.ui.Error ) {
+ // Use rejected promise for error
+ return $.Deferred().reject( [ result ] ).promise();
+ }
+ if ( Array.isArray( result ) && result.length && result[ 0 ] instanceof OO.ui.Error ) {
+ // Use rejected promise for list of errors
+ return $.Deferred().reject( result ).promise();
+ }
+ // Duck-type the object to see if it can produce a promise
+ if ( result && $.isFunction( result.promise ) ) {
+ // Use a promise generated from the result
+ return result.promise();
+ }
+ // Use resolved promise for other results
+ return $.Deferred().resolve().promise();
+ };
+ }
+
+ if ( this.steps.length ) {
+ // Generate a chain reaction of promises
+ promise = proceed( this.steps[ 0 ] )();
+ for ( i = 1, len = this.steps.length; i < len; i++ ) {
+ promise = promise.then( proceed( this.steps[ i ] ) );
+ }
+ } else {
+ promise = $.Deferred().resolve().promise();
+ }
+
+ return promise;
+};
+
+/**
+ * Create a process step.
+ *
+ * @private
+ * @param {number|jQuery.Promise|Function} step
+ *
+ * - Number of milliseconds to wait before proceeding
+ * - Promise that must be resolved before proceeding
+ * - Function to execute
+ * - If the function returns a boolean false the process will stop
+ * - If the function returns a promise, the process will continue to the next
+ * step when the promise is resolved or stop if the promise is rejected
+ * - If the function returns a number, the process will wait for that number of
+ * milliseconds before proceeding
+ * @param {Object} [context=null] Execution context of the function. The context is
+ * ignored if the step is a number or promise.
+ * @return {Object} Step object, with `callback` and `context` properties
+ */
+OO.ui.Process.prototype.createStep = function ( step, context ) {
+ if ( typeof step === 'number' || $.isFunction( step.promise ) ) {
+ return {
+ callback: function () {
+ return step;
+ },
+ context: null
+ };
+ }
+ if ( $.isFunction( step ) ) {
+ return {
+ callback: step,
+ context: context
+ };
+ }
+ throw new Error( 'Cannot create process step: number, promise or function expected' );
+};
+
+/**
+ * Add step to the beginning of the process.
+ *
+ * @inheritdoc #createStep
+ * @return {OO.ui.Process} this
+ * @chainable
+ */
+OO.ui.Process.prototype.first = function ( step, context ) {
+ this.steps.unshift( this.createStep( step, context ) );
+ return this;
+};
+
+/**
+ * Add step to the end of the process.
+ *
+ * @inheritdoc #createStep
+ * @return {OO.ui.Process} this
+ * @chainable
+ */
+OO.ui.Process.prototype.next = function ( step, context ) {
+ this.steps.push( this.createStep( step, context ) );
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/Theme.js b/vendor/oojs/oojs-ui/src/Theme.js
new file mode 100644
index 00000000..92de72ed
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Theme.js
@@ -0,0 +1,48 @@
+/**
+ * Theme logic.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.Theme = function OoUiTheme( config ) {
+ // Configuration initialization
+ config = config || {};
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.Theme );
+
+/* Methods */
+
+/**
+ * Get a list of classes to be applied to a widget.
+ *
+ * The 'on' and 'off' lists combined MUST contain keys for all classes the theme adds or removes,
+ * otherwise state transitions will not work properly.
+ *
+ * @param {OO.ui.Element} element Element for which to get classes
+ * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
+ */
+OO.ui.Theme.prototype.getElementClasses = function ( /* element */ ) {
+ return { on: [], off: [] };
+};
+
+/**
+ * Update CSS classes provided by the theme.
+ *
+ * For elements with theme logic hooks, this should be called any time there's a state change.
+ *
+ * @param {OO.ui.Element} element Element for which to update classes
+ * @return {Object.<string,string[]>} Categorized class names with `on` and `off` lists
+ */
+OO.ui.Theme.prototype.updateElementClasses = function ( element ) {
+ var classes = this.getElementClasses( element );
+
+ element.$element
+ .removeClass( classes.off.join( ' ' ) )
+ .addClass( classes.on.join( ' ' ) );
+};
diff --git a/vendor/oojs/oojs-ui/src/Tool.js b/vendor/oojs/oojs-ui/src/Tool.js
new file mode 100644
index 00000000..603c0639
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Tool.js
@@ -0,0 +1,279 @@
+/**
+ * Generic toolbar tool.
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.FlaggedElement
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {OO.ui.ToolGroup} toolGroup
+ * @param {Object} [config] Configuration options
+ * @cfg {string|Function} [title] Title text or a function that returns text
+ */
+OO.ui.Tool = function OoUiTool( toolGroup, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
+ config = toolGroup;
+ toolGroup = config.toolGroup;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.Tool.super.call( this, config );
+
+ // Properties
+ this.toolGroup = toolGroup;
+ this.toolbar = this.toolGroup.getToolbar();
+ this.active = false;
+ this.$title = $( '<span>' );
+ this.$accel = $( '<span>' );
+ this.$link = $( '<a>' );
+ this.title = null;
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, config );
+ OO.ui.FlaggedElement.call( this, config );
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$link } ) );
+
+ // Events
+ this.toolbar.connect( this, { updateState: 'onUpdateState' } );
+
+ // Initialization
+ this.$title.addClass( 'oo-ui-tool-title' );
+ this.$accel
+ .addClass( 'oo-ui-tool-accel' )
+ .prop( {
+ // This may need to be changed if the key names are ever localized,
+ // but for now they are essentially written in English
+ dir: 'ltr',
+ lang: 'en'
+ } );
+ this.$link
+ .addClass( 'oo-ui-tool-link' )
+ .append( this.$icon, this.$title, this.$accel )
+ .attr( 'role', 'button' );
+ this.$element
+ .data( 'oo-ui-tool', this )
+ .addClass(
+ 'oo-ui-tool ' + 'oo-ui-tool-name-' +
+ this.constructor.static.name.replace( /^([^\/]+)\/([^\/]+).*$/, '$1-$2' )
+ )
+ .toggleClass( 'oo-ui-tool-with-label', this.constructor.static.displayBothIconAndLabel )
+ .append( this.$link );
+ this.setTitle( config.title || this.constructor.static.title );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.Tool, OO.ui.Widget );
+OO.mixinClass( OO.ui.Tool, OO.ui.IconElement );
+OO.mixinClass( OO.ui.Tool, OO.ui.FlaggedElement );
+OO.mixinClass( OO.ui.Tool, OO.ui.TabIndexedElement );
+
+/* Events */
+
+/**
+ * @event select
+ */
+
+/* Static Properties */
+
+/**
+ * @static
+ * @inheritdoc
+ */
+OO.ui.Tool.static.tagName = 'span';
+
+/**
+ * Symbolic name of tool.
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.Tool.static.name = '';
+
+/**
+ * Tool group.
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.Tool.static.group = '';
+
+/**
+ * Tool title.
+ *
+ * Title is used as a tooltip when the tool is part of a bar tool group, or a label when the tool
+ * is part of a list or menu tool group. If a trigger is associated with an action by the same name
+ * as the tool, a description of its keyboard shortcut for the appropriate platform will be
+ * appended to the title if the tool is part of a bar tool group.
+ *
+ * @abstract
+ * @static
+ * @inheritable
+ * @property {string|Function} Title text or a function that returns text
+ */
+OO.ui.Tool.static.title = '';
+
+/**
+ * Whether this tool should be displayed with both title and label when used in a bar tool group.
+ * Normally only the icon is displayed, or only the label if no icon is given.
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.Tool.static.displayBothIconAndLabel = false;
+
+/**
+ * Tool can be automatically added to catch-all groups.
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.Tool.static.autoAddToCatchall = true;
+
+/**
+ * Tool can be automatically added to named groups.
+ *
+ * @static
+ * @property {boolean}
+ * @inheritable
+ */
+OO.ui.Tool.static.autoAddToGroup = true;
+
+/**
+ * Check if this tool is compatible with given data.
+ *
+ * @static
+ * @inheritable
+ * @param {Mixed} data Data to check
+ * @return {boolean} Tool can be used with data
+ */
+OO.ui.Tool.static.isCompatibleWith = function () {
+ return false;
+};
+
+/* Methods */
+
+/**
+ * Handle the toolbar state being updated.
+ *
+ * This is an abstract method that must be overridden in a concrete subclass.
+ *
+ * @abstract
+ */
+OO.ui.Tool.prototype.onUpdateState = function () {
+ throw new Error(
+ 'OO.ui.Tool.onUpdateState not implemented in this subclass:' + this.constructor
+ );
+};
+
+/**
+ * Handle the tool being selected.
+ *
+ * This is an abstract method that must be overridden in a concrete subclass.
+ *
+ * @abstract
+ */
+OO.ui.Tool.prototype.onSelect = function () {
+ throw new Error(
+ 'OO.ui.Tool.onSelect not implemented in this subclass:' + this.constructor
+ );
+};
+
+/**
+ * Check if the button is active.
+ *
+ * @return {boolean} Button is active
+ */
+OO.ui.Tool.prototype.isActive = function () {
+ return this.active;
+};
+
+/**
+ * Make the button appear active or inactive.
+ *
+ * @param {boolean} state Make button appear active
+ */
+OO.ui.Tool.prototype.setActive = function ( state ) {
+ this.active = !!state;
+ if ( this.active ) {
+ this.$element.addClass( 'oo-ui-tool-active' );
+ } else {
+ this.$element.removeClass( 'oo-ui-tool-active' );
+ }
+};
+
+/**
+ * Get the tool title.
+ *
+ * @param {string|Function} title Title text or a function that returns text
+ * @chainable
+ */
+OO.ui.Tool.prototype.setTitle = function ( title ) {
+ this.title = OO.ui.resolveMsg( title );
+ this.updateTitle();
+ return this;
+};
+
+/**
+ * Get the tool title.
+ *
+ * @return {string} Title text
+ */
+OO.ui.Tool.prototype.getTitle = function () {
+ return this.title;
+};
+
+/**
+ * Get the tool's symbolic name.
+ *
+ * @return {string} Symbolic name of tool
+ */
+OO.ui.Tool.prototype.getName = function () {
+ return this.constructor.static.name;
+};
+
+/**
+ * Update the title.
+ */
+OO.ui.Tool.prototype.updateTitle = function () {
+ var titleTooltips = this.toolGroup.constructor.static.titleTooltips,
+ accelTooltips = this.toolGroup.constructor.static.accelTooltips,
+ accel = this.toolbar.getToolAccelerator( this.constructor.static.name ),
+ tooltipParts = [];
+
+ this.$title.text( this.title );
+ this.$accel.text( accel );
+
+ if ( titleTooltips && typeof this.title === 'string' && this.title.length ) {
+ tooltipParts.push( this.title );
+ }
+ if ( accelTooltips && typeof accel === 'string' && accel.length ) {
+ tooltipParts.push( accel );
+ }
+ if ( tooltipParts.length ) {
+ this.$link.attr( 'title', tooltipParts.join( ' ' ) );
+ } else {
+ this.$link.removeAttr( 'title' );
+ }
+};
+
+/**
+ * Destroy tool.
+ */
+OO.ui.Tool.prototype.destroy = function () {
+ this.toolbar.disconnect( this );
+ this.$element.remove();
+};
diff --git a/vendor/oojs/oojs-ui/src/ToolFactory.js b/vendor/oojs/oojs-ui/src/ToolFactory.js
new file mode 100644
index 00000000..aada2e9b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/ToolFactory.js
@@ -0,0 +1,120 @@
+/**
+ * Factory for tools.
+ *
+ * @class
+ * @extends OO.Factory
+ * @constructor
+ */
+OO.ui.ToolFactory = function OoUiToolFactory() {
+ // Parent constructor
+ OO.ui.ToolFactory.super.call( this );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ToolFactory, OO.Factory );
+
+/* Methods */
+
+/**
+ * Get tools from the factory
+ *
+ * @param {Array} include Included tools
+ * @param {Array} exclude Excluded tools
+ * @param {Array} promote Promoted tools
+ * @param {Array} demote Demoted tools
+ * @return {string[]} List of tools
+ */
+OO.ui.ToolFactory.prototype.getTools = function ( include, exclude, promote, demote ) {
+ var i, len, included, promoted, demoted,
+ auto = [],
+ used = {};
+
+ // Collect included and not excluded tools
+ included = OO.simpleArrayDifference( this.extract( include ), this.extract( exclude ) );
+
+ // Promotion
+ promoted = this.extract( promote, used );
+ demoted = this.extract( demote, used );
+
+ // Auto
+ for ( i = 0, len = included.length; i < len; i++ ) {
+ if ( !used[ included[ i ] ] ) {
+ auto.push( included[ i ] );
+ }
+ }
+
+ return promoted.concat( auto ).concat( demoted );
+};
+
+/**
+ * Get a flat list of names from a list of names or groups.
+ *
+ * Tools can be specified in the following ways:
+ *
+ * - A specific tool: `{ name: 'tool-name' }` or `'tool-name'`
+ * - All tools in a group: `{ group: 'group-name' }`
+ * - All tools: `'*'`
+ *
+ * @private
+ * @param {Array|string} collection List of tools
+ * @param {Object} [used] Object with names that should be skipped as properties; extracted
+ * names will be added as properties
+ * @return {string[]} List of extracted names
+ */
+OO.ui.ToolFactory.prototype.extract = function ( collection, used ) {
+ var i, len, item, name, tool,
+ names = [];
+
+ if ( collection === '*' ) {
+ for ( name in this.registry ) {
+ tool = this.registry[ name ];
+ if (
+ // Only add tools by group name when auto-add is enabled
+ tool.static.autoAddToCatchall &&
+ // Exclude already used tools
+ ( !used || !used[ name ] )
+ ) {
+ names.push( name );
+ if ( used ) {
+ used[ name ] = true;
+ }
+ }
+ }
+ } else if ( Array.isArray( collection ) ) {
+ for ( i = 0, len = collection.length; i < len; i++ ) {
+ item = collection[ i ];
+ // Allow plain strings as shorthand for named tools
+ if ( typeof item === 'string' ) {
+ item = { name: item };
+ }
+ if ( OO.isPlainObject( item ) ) {
+ if ( item.group ) {
+ for ( name in this.registry ) {
+ tool = this.registry[ name ];
+ if (
+ // Include tools with matching group
+ tool.static.group === item.group &&
+ // Only add tools by group name when auto-add is enabled
+ tool.static.autoAddToGroup &&
+ // Exclude already used tools
+ ( !used || !used[ name ] )
+ ) {
+ names.push( name );
+ if ( used ) {
+ used[ name ] = true;
+ }
+ }
+ }
+ // Include tools with matching name and exclude already used tools
+ } else if ( item.name && ( !used || !used[ item.name ] ) ) {
+ names.push( item.name );
+ if ( used ) {
+ used[ item.name ] = true;
+ }
+ }
+ }
+ }
+ }
+ return names;
+};
diff --git a/vendor/oojs/oojs-ui/src/ToolGroup.js b/vendor/oojs/oojs-ui/src/ToolGroup.js
new file mode 100644
index 00000000..eab3cea4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/ToolGroup.js
@@ -0,0 +1,338 @@
+/**
+ * Collection of tools.
+ *
+ * Tools can be specified in the following ways:
+ *
+ * - A specific tool: `{ name: 'tool-name' }` or `'tool-name'`
+ * - All tools in a group: `{ group: 'group-name' }`
+ * - All tools: `'*'`
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
+ * @cfg {Array|string} [include=[]] List of tools to include
+ * @cfg {Array|string} [exclude=[]] List of tools to exclude
+ * @cfg {Array|string} [promote=[]] List of tools to promote to the beginning
+ * @cfg {Array|string} [demote=[]] List of tools to demote to the end
+ */
+OO.ui.ToolGroup = function OoUiToolGroup( toolbar, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+ config = toolbar;
+ toolbar = config.toolbar;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ToolGroup.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.GroupElement.call( this, config );
+
+ // Properties
+ this.toolbar = toolbar;
+ this.tools = {};
+ this.pressed = null;
+ this.autoDisabled = false;
+ this.include = config.include || [];
+ this.exclude = config.exclude || [];
+ this.promote = config.promote || [];
+ this.demote = config.demote || [];
+ this.onCapturedMouseKeyUpHandler = this.onCapturedMouseKeyUp.bind( this );
+
+ // Events
+ this.$element.on( {
+ mousedown: this.onMouseKeyDown.bind( this ),
+ mouseup: this.onMouseKeyUp.bind( this ),
+ keydown: this.onMouseKeyDown.bind( this ),
+ keyup: this.onMouseKeyUp.bind( this ),
+ focus: this.onMouseOverFocus.bind( this ),
+ blur: this.onMouseOutBlur.bind( this ),
+ mouseover: this.onMouseOverFocus.bind( this ),
+ mouseout: this.onMouseOutBlur.bind( this )
+ } );
+ this.toolbar.getToolFactory().connect( this, { register: 'onToolFactoryRegister' } );
+ this.aggregate( { disable: 'itemDisable' } );
+ this.connect( this, { itemDisable: 'updateDisabled' } );
+
+ // Initialization
+ this.$group.addClass( 'oo-ui-toolGroup-tools' );
+ this.$element
+ .addClass( 'oo-ui-toolGroup' )
+ .append( this.$group );
+ this.populate();
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ToolGroup, OO.ui.Widget );
+OO.mixinClass( OO.ui.ToolGroup, OO.ui.GroupElement );
+
+/* Events */
+
+/**
+ * @event update
+ */
+
+/* Static Properties */
+
+/**
+ * Show labels in tooltips.
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.ToolGroup.static.titleTooltips = false;
+
+/**
+ * Show acceleration labels in tooltips.
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.ToolGroup.static.accelTooltips = false;
+
+/**
+ * Automatically disable the toolgroup when all tools are disabled
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.ToolGroup.static.autoDisable = true;
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ToolGroup.prototype.isDisabled = function () {
+ return this.autoDisabled || OO.ui.ToolGroup.super.prototype.isDisabled.apply( this, arguments );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ToolGroup.prototype.updateDisabled = function () {
+ var i, item, allDisabled = true;
+
+ if ( this.constructor.static.autoDisable ) {
+ for ( i = this.items.length - 1; i >= 0; i-- ) {
+ item = this.items[ i ];
+ if ( !item.isDisabled() ) {
+ allDisabled = false;
+ break;
+ }
+ }
+ this.autoDisabled = allDisabled;
+ }
+ OO.ui.ToolGroup.super.prototype.updateDisabled.apply( this, arguments );
+};
+
+/**
+ * Handle mouse down and key down events.
+ *
+ * @param {jQuery.Event} e Mouse down or key down event
+ */
+OO.ui.ToolGroup.prototype.onMouseKeyDown = function ( e ) {
+ if (
+ !this.isDisabled() &&
+ ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+ ) {
+ this.pressed = this.getTargetTool( e );
+ if ( this.pressed ) {
+ this.pressed.setActive( true );
+ this.getElementDocument().addEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
+ this.getElementDocument().addEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, true );
+ }
+ return false;
+ }
+};
+
+/**
+ * Handle captured mouse up and key up events.
+ *
+ * @param {Event} e Mouse up or key up event
+ */
+OO.ui.ToolGroup.prototype.onCapturedMouseKeyUp = function ( e ) {
+ this.getElementDocument().removeEventListener( 'mouseup', this.onCapturedMouseKeyUpHandler, true );
+ this.getElementDocument().removeEventListener( 'keyup', this.onCapturedMouseKeyUpHandler, true );
+ // onMouseKeyUp may be called a second time, depending on where the mouse is when the button is
+ // released, but since `this.pressed` will no longer be true, the second call will be ignored.
+ this.onMouseKeyUp( e );
+};
+
+/**
+ * Handle mouse up and key up events.
+ *
+ * @param {jQuery.Event} e Mouse up or key up event
+ */
+OO.ui.ToolGroup.prototype.onMouseKeyUp = function ( e ) {
+ var tool = this.getTargetTool( e );
+
+ if (
+ !this.isDisabled() && this.pressed && this.pressed === tool &&
+ ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+ ) {
+ this.pressed.onSelect();
+ this.pressed = null;
+ return false;
+ }
+
+ this.pressed = null;
+};
+
+/**
+ * Handle mouse over and focus events.
+ *
+ * @param {jQuery.Event} e Mouse over or focus event
+ */
+OO.ui.ToolGroup.prototype.onMouseOverFocus = function ( e ) {
+ var tool = this.getTargetTool( e );
+
+ if ( this.pressed && this.pressed === tool ) {
+ this.pressed.setActive( true );
+ }
+};
+
+/**
+ * Handle mouse out and blur events.
+ *
+ * @param {jQuery.Event} e Mouse out or blur event
+ */
+OO.ui.ToolGroup.prototype.onMouseOutBlur = function ( e ) {
+ var tool = this.getTargetTool( e );
+
+ if ( this.pressed && this.pressed === tool ) {
+ this.pressed.setActive( false );
+ }
+};
+
+/**
+ * Get the closest tool to a jQuery.Event.
+ *
+ * Only tool links are considered, which prevents other elements in the tool such as popups from
+ * triggering tool group interactions.
+ *
+ * @private
+ * @param {jQuery.Event} e
+ * @return {OO.ui.Tool|null} Tool, `null` if none was found
+ */
+OO.ui.ToolGroup.prototype.getTargetTool = function ( e ) {
+ var tool,
+ $item = $( e.target ).closest( '.oo-ui-tool-link' );
+
+ if ( $item.length ) {
+ tool = $item.parent().data( 'oo-ui-tool' );
+ }
+
+ return tool && !tool.isDisabled() ? tool : null;
+};
+
+/**
+ * Handle tool registry register events.
+ *
+ * If a tool is registered after the group is created, we must repopulate the list to account for:
+ *
+ * - a tool being added that may be included
+ * - a tool already included being overridden
+ *
+ * @param {string} name Symbolic name of tool
+ */
+OO.ui.ToolGroup.prototype.onToolFactoryRegister = function () {
+ this.populate();
+};
+
+/**
+ * Get the toolbar this group is in.
+ *
+ * @return {OO.ui.Toolbar} Toolbar of group
+ */
+OO.ui.ToolGroup.prototype.getToolbar = function () {
+ return this.toolbar;
+};
+
+/**
+ * Add and remove tools based on configuration.
+ */
+OO.ui.ToolGroup.prototype.populate = function () {
+ var i, len, name, tool,
+ toolFactory = this.toolbar.getToolFactory(),
+ names = {},
+ add = [],
+ remove = [],
+ list = this.toolbar.getToolFactory().getTools(
+ this.include, this.exclude, this.promote, this.demote
+ );
+
+ // Build a list of needed tools
+ for ( i = 0, len = list.length; i < len; i++ ) {
+ name = list[ i ];
+ if (
+ // Tool exists
+ toolFactory.lookup( name ) &&
+ // Tool is available or is already in this group
+ ( this.toolbar.isToolAvailable( name ) || this.tools[ name ] )
+ ) {
+ // Hack to prevent infinite recursion via ToolGroupTool. We need to reserve the tool before
+ // creating it, but we can't call reserveTool() yet because we haven't created the tool.
+ this.toolbar.tools[ name ] = true;
+ tool = this.tools[ name ];
+ if ( !tool ) {
+ // Auto-initialize tools on first use
+ this.tools[ name ] = tool = toolFactory.create( name, this );
+ tool.updateTitle();
+ }
+ this.toolbar.reserveTool( tool );
+ add.push( tool );
+ names[ name ] = true;
+ }
+ }
+ // Remove tools that are no longer needed
+ for ( name in this.tools ) {
+ if ( !names[ name ] ) {
+ this.tools[ name ].destroy();
+ this.toolbar.releaseTool( this.tools[ name ] );
+ remove.push( this.tools[ name ] );
+ delete this.tools[ name ];
+ }
+ }
+ if ( remove.length ) {
+ this.removeItems( remove );
+ }
+ // Update emptiness state
+ if ( add.length ) {
+ this.$element.removeClass( 'oo-ui-toolGroup-empty' );
+ } else {
+ this.$element.addClass( 'oo-ui-toolGroup-empty' );
+ }
+ // Re-add tools (moving existing ones to new locations)
+ this.addItems( add );
+ // Disabled state may depend on items
+ this.updateDisabled();
+};
+
+/**
+ * Destroy tool group.
+ */
+OO.ui.ToolGroup.prototype.destroy = function () {
+ var name;
+
+ this.clearItems();
+ this.toolbar.getToolFactory().disconnect( this );
+ for ( name in this.tools ) {
+ this.toolbar.releaseTool( this.tools[ name ] );
+ this.tools[ name ].disconnect( this ).destroy();
+ delete this.tools[ name ];
+ }
+ this.$element.remove();
+};
diff --git a/vendor/oojs/oojs-ui/src/ToolGroupFactory.js b/vendor/oojs/oojs-ui/src/ToolGroupFactory.js
new file mode 100644
index 00000000..109b86e8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/ToolGroupFactory.js
@@ -0,0 +1,38 @@
+/**
+ * Factory for tool groups.
+ *
+ * @class
+ * @extends OO.Factory
+ * @constructor
+ */
+OO.ui.ToolGroupFactory = function OoUiToolGroupFactory() {
+ // Parent constructor
+ OO.Factory.call( this );
+
+ var i, l,
+ defaultClasses = this.constructor.static.getDefaultClasses();
+
+ // Register default toolgroups
+ for ( i = 0, l = defaultClasses.length; i < l; i++ ) {
+ this.register( defaultClasses[ i ] );
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ToolGroupFactory, OO.Factory );
+
+/* Static Methods */
+
+/**
+ * Get a default set of classes to be registered on construction
+ *
+ * @return {Function[]} Default classes
+ */
+OO.ui.ToolGroupFactory.static.getDefaultClasses = function () {
+ return [
+ OO.ui.BarToolGroup,
+ OO.ui.ListToolGroup,
+ OO.ui.MenuToolGroup
+ ];
+};
diff --git a/vendor/oojs/oojs-ui/src/Toolbar.js b/vendor/oojs/oojs-ui/src/Toolbar.js
new file mode 100644
index 00000000..f52fbffe
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Toolbar.js
@@ -0,0 +1,481 @@
+/**
+ * Collection of tool groups.
+ *
+ * The following is a minimal example using several tools and tool groups.
+ *
+ * @example
+ * // Create the toolbar
+ * var toolFactory = new OO.ui.ToolFactory();
+ * var toolGroupFactory = new OO.ui.ToolGroupFactory();
+ * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
+ *
+ * // We will be placing status text in this element when tools are used
+ * var $area = $( '<p>' ).text( 'Toolbar example' );
+ *
+ * // Define the tools that we're going to place in our toolbar
+ *
+ * // Create a class inheriting from OO.ui.Tool
+ * function PictureTool() {
+ * PictureTool.super.apply( this, arguments );
+ * }
+ * OO.inheritClass( PictureTool, OO.ui.Tool );
+ * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
+ * // of 'icon' and 'title' (displayed icon and text).
+ * PictureTool.static.name = 'picture';
+ * PictureTool.static.icon = 'picture';
+ * PictureTool.static.title = 'Insert picture';
+ * // Defines the action that will happen when this tool is selected (clicked).
+ * PictureTool.prototype.onSelect = function () {
+ * $area.text( 'Picture tool clicked!' );
+ * // Never display this tool as "active" (selected).
+ * this.setActive( false );
+ * };
+ * // Make this tool available in our toolFactory and thus our toolbar
+ * toolFactory.register( PictureTool );
+ *
+ * // Register two more tools, nothing interesting here
+ * function SettingsTool() {
+ * SettingsTool.super.apply( this, arguments );
+ * }
+ * OO.inheritClass( SettingsTool, OO.ui.Tool );
+ * SettingsTool.static.name = 'settings';
+ * SettingsTool.static.icon = 'settings';
+ * SettingsTool.static.title = 'Change settings';
+ * SettingsTool.prototype.onSelect = function () {
+ * $area.text( 'Settings tool clicked!' );
+ * this.setActive( false );
+ * };
+ * toolFactory.register( SettingsTool );
+ *
+ * // Register two more tools, nothing interesting here
+ * function StuffTool() {
+ * StuffTool.super.apply( this, arguments );
+ * }
+ * OO.inheritClass( StuffTool, OO.ui.Tool );
+ * StuffTool.static.name = 'stuff';
+ * StuffTool.static.icon = 'ellipsis';
+ * StuffTool.static.title = 'More stuff';
+ * StuffTool.prototype.onSelect = function () {
+ * $area.text( 'More stuff tool clicked!' );
+ * this.setActive( false );
+ * };
+ * toolFactory.register( StuffTool );
+ *
+ * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
+ * // little popup window (a PopupWidget).
+ * function HelpTool( toolGroup, config ) {
+ * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
+ * padded: true,
+ * label: 'Help',
+ * head: true
+ * } }, config ) );
+ * this.popup.$body.append( '<p>I am helpful!</p>' );
+ * }
+ * OO.inheritClass( HelpTool, OO.ui.PopupTool );
+ * HelpTool.static.name = 'help';
+ * HelpTool.static.icon = 'help';
+ * HelpTool.static.title = 'Help';
+ * toolFactory.register( HelpTool );
+ *
+ * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
+ * // used once (but not all defined tools must be used).
+ * toolbar.setup( [
+ * {
+ * // 'bar' tool groups display tools' icons only, side-by-side.
+ * type: 'bar',
+ * include: [ 'picture', 'help' ]
+ * },
+ * {
+ * // 'list' tool groups display both the titles and icons, in a dropdown list.
+ * type: 'list',
+ * indicator: 'down',
+ * label: 'More',
+ * include: [ 'settings', 'stuff' ]
+ * }
+ * // Note how the tools themselves are toolgroup-agnostic - the same tool can be displayed
+ * // either in a 'list' or a 'bar'. There is a 'menu' tool group too, not showcased here,
+ * // since it's more complicated to use. (See the next example snippet on this page.)
+ * ] );
+ *
+ * // Create some UI around the toolbar and place it in the document
+ * var frame = new OO.ui.PanelLayout( {
+ * expanded: false,
+ * framed: true
+ * } );
+ * var contentFrame = new OO.ui.PanelLayout( {
+ * expanded: false,
+ * padded: true
+ * } );
+ * frame.$element.append(
+ * toolbar.$element,
+ * contentFrame.$element.append( $area )
+ * );
+ * $( 'body' ).append( frame.$element );
+ *
+ * // Here is where the toolbar is actually built. This must be done after inserting it into the
+ * // document.
+ * toolbar.initialize();
+ *
+ * The following example extends the previous one to illustrate 'menu' tool groups and the usage of
+ * 'updateState' event.
+ *
+ * @example
+ * // Create the toolbar
+ * var toolFactory = new OO.ui.ToolFactory();
+ * var toolGroupFactory = new OO.ui.ToolGroupFactory();
+ * var toolbar = new OO.ui.Toolbar( toolFactory, toolGroupFactory );
+ *
+ * // We will be placing status text in this element when tools are used
+ * var $area = $( '<p>' ).text( 'Toolbar example' );
+ *
+ * // Define the tools that we're going to place in our toolbar
+ *
+ * // Create a class inheriting from OO.ui.Tool
+ * function PictureTool() {
+ * PictureTool.super.apply( this, arguments );
+ * }
+ * OO.inheritClass( PictureTool, OO.ui.Tool );
+ * // Each tool must have a 'name' (used as an internal identifier, see later) and at least one
+ * // of 'icon' and 'title' (displayed icon and text).
+ * PictureTool.static.name = 'picture';
+ * PictureTool.static.icon = 'picture';
+ * PictureTool.static.title = 'Insert picture';
+ * // Defines the action that will happen when this tool is selected (clicked).
+ * PictureTool.prototype.onSelect = function () {
+ * $area.text( 'Picture tool clicked!' );
+ * // Never display this tool as "active" (selected).
+ * this.setActive( false );
+ * };
+ * // The toolbar can be synchronized with the state of some external stuff, like a text
+ * // editor's editing area, highlighting the tools (e.g. a 'bold' tool would be shown as active
+ * // when the text cursor was inside bolded text). Here we simply disable this feature.
+ * PictureTool.prototype.onUpdateState = function () {
+ * };
+ * // Make this tool available in our toolFactory and thus our toolbar
+ * toolFactory.register( PictureTool );
+ *
+ * // Register two more tools, nothing interesting here
+ * function SettingsTool() {
+ * SettingsTool.super.apply( this, arguments );
+ * this.reallyActive = false;
+ * }
+ * OO.inheritClass( SettingsTool, OO.ui.Tool );
+ * SettingsTool.static.name = 'settings';
+ * SettingsTool.static.icon = 'settings';
+ * SettingsTool.static.title = 'Change settings';
+ * SettingsTool.prototype.onSelect = function () {
+ * $area.text( 'Settings tool clicked!' );
+ * // Toggle the active state on each click
+ * this.reallyActive = !this.reallyActive;
+ * this.setActive( this.reallyActive );
+ * // To update the menu label
+ * this.toolbar.emit( 'updateState' );
+ * };
+ * SettingsTool.prototype.onUpdateState = function () {
+ * };
+ * toolFactory.register( SettingsTool );
+ *
+ * // Register two more tools, nothing interesting here
+ * function StuffTool() {
+ * StuffTool.super.apply( this, arguments );
+ * this.reallyActive = false;
+ * }
+ * OO.inheritClass( StuffTool, OO.ui.Tool );
+ * StuffTool.static.name = 'stuff';
+ * StuffTool.static.icon = 'ellipsis';
+ * StuffTool.static.title = 'More stuff';
+ * StuffTool.prototype.onSelect = function () {
+ * $area.text( 'More stuff tool clicked!' );
+ * // Toggle the active state on each click
+ * this.reallyActive = !this.reallyActive;
+ * this.setActive( this.reallyActive );
+ * // To update the menu label
+ * this.toolbar.emit( 'updateState' );
+ * };
+ * StuffTool.prototype.onUpdateState = function () {
+ * };
+ * toolFactory.register( StuffTool );
+ *
+ * // This is a PopupTool. Rather than having a custom 'onSelect' action, it will display a
+ * // little popup window (a PopupWidget). 'onUpdateState' is also already implemented.
+ * function HelpTool( toolGroup, config ) {
+ * OO.ui.PopupTool.call( this, toolGroup, $.extend( { popup: {
+ * padded: true,
+ * label: 'Help',
+ * head: true
+ * } }, config ) );
+ * this.popup.$body.append( '<p>I am helpful!</p>' );
+ * }
+ * OO.inheritClass( HelpTool, OO.ui.PopupTool );
+ * HelpTool.static.name = 'help';
+ * HelpTool.static.icon = 'help';
+ * HelpTool.static.title = 'Help';
+ * toolFactory.register( HelpTool );
+ *
+ * // Finally define which tools and in what order appear in the toolbar. Each tool may only be
+ * // used once (but not all defined tools must be used).
+ * toolbar.setup( [
+ * {
+ * // 'bar' tool groups display tools' icons only, side-by-side.
+ * type: 'bar',
+ * include: [ 'picture', 'help' ]
+ * },
+ * {
+ * // 'menu' tool groups display both the titles and icons, in a dropdown menu.
+ * // Menu label indicates which items are selected.
+ * type: 'menu',
+ * indicator: 'down',
+ * include: [ 'settings', 'stuff' ]
+ * }
+ * ] );
+ *
+ * // Create some UI around the toolbar and place it in the document
+ * var frame = new OO.ui.PanelLayout( {
+ * expanded: false,
+ * framed: true
+ * } );
+ * var contentFrame = new OO.ui.PanelLayout( {
+ * expanded: false,
+ * padded: true
+ * } );
+ * frame.$element.append(
+ * toolbar.$element,
+ * contentFrame.$element.append( $area )
+ * );
+ * $( 'body' ).append( frame.$element );
+ *
+ * // Here is where the toolbar is actually built. This must be done after inserting it into the
+ * // document.
+ * toolbar.initialize();
+ * toolbar.emit( 'updateState' );
+ *
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {OO.ui.ToolFactory} toolFactory Factory for creating tools
+ * @param {OO.ui.ToolGroupFactory} toolGroupFactory Factory for creating tool groups
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [actions] Add an actions section opposite to the tools
+ * @cfg {boolean} [shadow] Add a shadow below the toolbar
+ */
+OO.ui.Toolbar = function OoUiToolbar( toolFactory, toolGroupFactory, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolFactory ) && config === undefined ) {
+ config = toolFactory;
+ toolFactory = config.toolFactory;
+ toolGroupFactory = config.toolGroupFactory;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.Toolbar.super.call( this, config );
+
+ // Mixin constructors
+ OO.EventEmitter.call( this );
+ OO.ui.GroupElement.call( this, config );
+
+ // Properties
+ this.toolFactory = toolFactory;
+ this.toolGroupFactory = toolGroupFactory;
+ this.groups = [];
+ this.tools = {};
+ this.$bar = $( '<div>' );
+ this.$actions = $( '<div>' );
+ this.initialized = false;
+ this.onWindowResizeHandler = this.onWindowResize.bind( this );
+
+ // Events
+ this.$element
+ .add( this.$bar ).add( this.$group ).add( this.$actions )
+ .on( 'mousedown keydown', this.onPointerDown.bind( this ) );
+
+ // Initialization
+ this.$group.addClass( 'oo-ui-toolbar-tools' );
+ if ( config.actions ) {
+ this.$bar.append( this.$actions.addClass( 'oo-ui-toolbar-actions' ) );
+ }
+ this.$bar
+ .addClass( 'oo-ui-toolbar-bar' )
+ .append( this.$group, '<div style="clear:both"></div>' );
+ if ( config.shadow ) {
+ this.$bar.append( '<div class="oo-ui-toolbar-shadow"></div>' );
+ }
+ this.$element.addClass( 'oo-ui-toolbar' ).append( this.$bar );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.Toolbar, OO.ui.Element );
+OO.mixinClass( OO.ui.Toolbar, OO.EventEmitter );
+OO.mixinClass( OO.ui.Toolbar, OO.ui.GroupElement );
+
+/* Methods */
+
+/**
+ * Get the tool factory.
+ *
+ * @return {OO.ui.ToolFactory} Tool factory
+ */
+OO.ui.Toolbar.prototype.getToolFactory = function () {
+ return this.toolFactory;
+};
+
+/**
+ * Get the tool group factory.
+ *
+ * @return {OO.Factory} Tool group factory
+ */
+OO.ui.Toolbar.prototype.getToolGroupFactory = function () {
+ return this.toolGroupFactory;
+};
+
+/**
+ * Handles mouse down events.
+ *
+ * @param {jQuery.Event} e Mouse down event
+ */
+OO.ui.Toolbar.prototype.onPointerDown = function ( e ) {
+ var $closestWidgetToEvent = $( e.target ).closest( '.oo-ui-widget' ),
+ $closestWidgetToToolbar = this.$element.closest( '.oo-ui-widget' );
+ if ( !$closestWidgetToEvent.length || $closestWidgetToEvent[ 0 ] === $closestWidgetToToolbar[ 0 ] ) {
+ return false;
+ }
+};
+
+/**
+ * Handle window resize event.
+ *
+ * @private
+ * @param {jQuery.Event} e Window resize event
+ */
+OO.ui.Toolbar.prototype.onWindowResize = function () {
+ this.$element.toggleClass(
+ 'oo-ui-toolbar-narrow',
+ this.$bar.width() <= this.narrowThreshold
+ );
+};
+
+/**
+ * Sets up handles and preloads required information for the toolbar to work.
+ * This must be called after it is attached to a visible document and before doing anything else.
+ */
+OO.ui.Toolbar.prototype.initialize = function () {
+ this.initialized = true;
+ this.narrowThreshold = this.$group.width() + this.$actions.width();
+ $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler );
+ this.onWindowResize();
+};
+
+/**
+ * Setup toolbar.
+ *
+ * Tools can be specified in the following ways:
+ *
+ * - A specific tool: `{ name: 'tool-name' }` or `'tool-name'`
+ * - All tools in a group: `{ group: 'group-name' }`
+ * - All tools: `'*'` - Using this will make the group a list with a "More" label by default
+ *
+ * @param {Object.<string,Array>} groups List of tool group configurations
+ * @param {Array|string} [groups.include] Tools to include
+ * @param {Array|string} [groups.exclude] Tools to exclude
+ * @param {Array|string} [groups.promote] Tools to promote to the beginning
+ * @param {Array|string} [groups.demote] Tools to demote to the end
+ */
+OO.ui.Toolbar.prototype.setup = function ( groups ) {
+ var i, len, type, group,
+ items = [],
+ defaultType = 'bar';
+
+ // Cleanup previous groups
+ this.reset();
+
+ // Build out new groups
+ for ( i = 0, len = groups.length; i < len; i++ ) {
+ group = groups[ i ];
+ if ( group.include === '*' ) {
+ // Apply defaults to catch-all groups
+ if ( group.type === undefined ) {
+ group.type = 'list';
+ }
+ if ( group.label === undefined ) {
+ group.label = OO.ui.msg( 'ooui-toolbar-more' );
+ }
+ }
+ // Check type has been registered
+ type = this.getToolGroupFactory().lookup( group.type ) ? group.type : defaultType;
+ items.push(
+ this.getToolGroupFactory().create( type, this, group )
+ );
+ }
+ this.addItems( items );
+};
+
+/**
+ * Remove all tools and groups from the toolbar.
+ */
+OO.ui.Toolbar.prototype.reset = function () {
+ var i, len;
+
+ this.groups = [];
+ this.tools = {};
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ this.items[ i ].destroy();
+ }
+ this.clearItems();
+};
+
+/**
+ * Destroys toolbar, removing event handlers and DOM elements.
+ *
+ * Call this whenever you are done using a toolbar.
+ */
+OO.ui.Toolbar.prototype.destroy = function () {
+ $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
+ this.reset();
+ this.$element.remove();
+};
+
+/**
+ * Check if tool has not been used yet.
+ *
+ * @param {string} name Symbolic name of tool
+ * @return {boolean} Tool is available
+ */
+OO.ui.Toolbar.prototype.isToolAvailable = function ( name ) {
+ return !this.tools[ name ];
+};
+
+/**
+ * Prevent tool from being used again.
+ *
+ * @param {OO.ui.Tool} tool Tool to reserve
+ */
+OO.ui.Toolbar.prototype.reserveTool = function ( tool ) {
+ this.tools[ tool.getName() ] = tool;
+};
+
+/**
+ * Allow tool to be used again.
+ *
+ * @param {OO.ui.Tool} tool Tool to release
+ */
+OO.ui.Toolbar.prototype.releaseTool = function ( tool ) {
+ delete this.tools[ tool.getName() ];
+};
+
+/**
+ * Get accelerator label for tool.
+ *
+ * This is a stub that should be overridden to provide access to accelerator information.
+ *
+ * @param {string} name Symbolic name of tool
+ * @return {string|undefined} Tool accelerator label if available
+ */
+OO.ui.Toolbar.prototype.getToolAccelerator = function () {
+ return undefined;
+};
diff --git a/vendor/oojs/oojs-ui/src/Widget.js b/vendor/oojs/oojs-ui/src/Widget.js
new file mode 100644
index 00000000..4929e055
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Widget.js
@@ -0,0 +1,102 @@
+/**
+ * Widgets are compositions of one or more OOjs UI elements that users can both view
+ * and interact with. All widgets can be configured and modified via a standard API,
+ * and their state can change dynamically according to a model.
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [disabled=false] Disable the widget. Disabled widgets cannot be used and their
+ * appearance reflects this state.
+ */
+OO.ui.Widget = function OoUiWidget( config ) {
+ // Initialize config
+ config = $.extend( { disabled: false }, config );
+
+ // Parent constructor
+ OO.ui.Widget.super.call( this, config );
+
+ // Mixin constructors
+ OO.EventEmitter.call( this );
+
+ // Properties
+ this.disabled = null;
+ this.wasDisabled = null;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-widget' );
+ this.setDisabled( !!config.disabled );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.Widget, OO.ui.Element );
+OO.mixinClass( OO.ui.Widget, OO.EventEmitter );
+
+/* Events */
+
+/**
+ * @event disable
+ *
+ * A 'disable' event is emitted when a widget is disabled.
+ *
+ * @param {boolean} disabled Widget is disabled
+ */
+
+/**
+ * @event toggle
+ *
+ * A 'toggle' event is emitted when the visibility of the widget changes.
+ *
+ * @param {boolean} visible Widget is visible
+ */
+
+/* Methods */
+
+/**
+ * Check if the widget is disabled.
+ *
+ * @return {boolean} Widget is disabled
+ */
+OO.ui.Widget.prototype.isDisabled = function () {
+ return this.disabled;
+};
+
+/**
+ * Set the 'disabled' state of the widget.
+ *
+ * When a widget is disabled, it cannot be used and its appearance is updated to reflect this state.
+ *
+ * @param {boolean} disabled Disable widget
+ * @chainable
+ */
+OO.ui.Widget.prototype.setDisabled = function ( disabled ) {
+ var isDisabled;
+
+ this.disabled = !!disabled;
+ isDisabled = this.isDisabled();
+ if ( isDisabled !== this.wasDisabled ) {
+ this.$element.toggleClass( 'oo-ui-widget-disabled', isDisabled );
+ this.$element.toggleClass( 'oo-ui-widget-enabled', !isDisabled );
+ this.$element.attr( 'aria-disabled', isDisabled.toString() );
+ this.emit( 'disable', isDisabled );
+ this.updateThemeClasses();
+ }
+ this.wasDisabled = isDisabled;
+
+ return this;
+};
+
+/**
+ * Update the disabled state, in case of changes in parent widget.
+ *
+ * @chainable
+ */
+OO.ui.Widget.prototype.updateDisabled = function () {
+ this.setDisabled( this.disabled );
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/Window.js b/vendor/oojs/oojs-ui/src/Window.js
new file mode 100644
index 00000000..c3c2e51d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/Window.js
@@ -0,0 +1,628 @@
+/**
+ * A window is a container for elements that are in a child frame. They are used with
+ * a window manager (OO.ui.WindowManager), which is used to open and close the window and control
+ * its presentation. The size of a window is specified using a symbolic name (e.g., ‘small’, ‘medium’,
+ * ‘large’), which is interpreted by the window manager. If the requested size is not recognized,
+ * the window manager will choose a sensible fallback.
+ *
+ * The lifecycle of a window has three primary stages (opening, opened, and closing) in which
+ * different processes are executed:
+ *
+ * **opening**: The opening stage begins when the window manager's {@link OO.ui.WindowManager#openWindow
+ * openWindow} or the window's {@link #open open} methods are used, and the window manager begins to open
+ * the window.
+ *
+ * - {@link #getSetupProcess} method is called and its result executed
+ * - {@link #getReadyProcess} method is called and its result executed
+ *
+ * **opened**: The window is now open
+ *
+ * **closing**: The closing stage begins when the window manager's
+ * {@link OO.ui.WindowManager#closeWindow closeWindow}
+ * or the window's {@link #close} methods are used, and the window manager begins to close the window.
+ *
+ * - {@link #getHoldProcess} method is called and its result executed
+ * - {@link #getTeardownProcess} method is called and its result executed. The window is now closed
+ *
+ * Each of the window's processes (setup, ready, hold, and teardown) can be extended in subclasses
+ * by overriding the window's #getSetupProcess, #getReadyProcess, #getHoldProcess and #getTeardownProcess
+ * methods. Note that each {@link OO.ui.Process process} is executed in series, so asynchronous
+ * processing can complete. Always assume window processes are executed asynchronously.
+ *
+ * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [size] Symbolic name of the dialog size: `small`, `medium`, `large`, `larger` or
+ * `full`. If omitted, the value of the {@link #static-size static size} property will be used.
+ */
+OO.ui.Window = function OoUiWindow( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.Window.super.call( this, config );
+
+ // Mixin constructors
+ OO.EventEmitter.call( this );
+
+ // Properties
+ this.manager = null;
+ this.size = config.size || this.constructor.static.size;
+ this.$frame = $( '<div>' );
+ this.$overlay = $( '<div>' );
+ this.$content = $( '<div>' );
+
+ // Initialization
+ this.$overlay.addClass( 'oo-ui-window-overlay' );
+ this.$content
+ .addClass( 'oo-ui-window-content' )
+ .attr( 'tabindex', 0 );
+ this.$frame
+ .addClass( 'oo-ui-window-frame' )
+ .append( this.$content );
+
+ this.$element
+ .addClass( 'oo-ui-window' )
+ .append( this.$frame, this.$overlay );
+
+ // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
+ // that reference properties not initialized at that time of parent class construction
+ // TODO: Find a better way to handle post-constructor setup
+ this.visible = false;
+ this.$element.addClass( 'oo-ui-element-hidden' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.Window, OO.ui.Element );
+OO.mixinClass( OO.ui.Window, OO.EventEmitter );
+
+/* Static Properties */
+
+/**
+ * Symbolic name of the window size: `small`, `medium`, `large`, `larger` or `full`.
+ *
+ * The static size is used if no #size is configured during construction.
+ *
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.Window.static.size = 'medium';
+
+/* Methods */
+
+/**
+ * Handle mouse down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse down event
+ */
+OO.ui.Window.prototype.onMouseDown = function ( e ) {
+ // Prevent clicking on the click-block from stealing focus
+ if ( e.target === this.$element[ 0 ] ) {
+ return false;
+ }
+};
+
+/**
+ * Check if the window has been initialized.
+ *
+ * Initialization occurs when a window is added to a manager.
+ *
+ * @return {boolean} Window has been initialized
+ */
+OO.ui.Window.prototype.isInitialized = function () {
+ return !!this.manager;
+};
+
+/**
+ * Check if the window is visible.
+ *
+ * @return {boolean} Window is visible
+ */
+OO.ui.Window.prototype.isVisible = function () {
+ return this.visible;
+};
+
+/**
+ * Check if the window is opening.
+ *
+ * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpening isOpening}
+ * method.
+ *
+ * @return {boolean} Window is opening
+ */
+OO.ui.Window.prototype.isOpening = function () {
+ return this.manager.isOpening( this );
+};
+
+/**
+ * Check if the window is closing.
+ *
+ * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isClosing isClosing} method.
+ *
+ * @return {boolean} Window is closing
+ */
+OO.ui.Window.prototype.isClosing = function () {
+ return this.manager.isClosing( this );
+};
+
+/**
+ * Check if the window is opened.
+ *
+ * This method is a wrapper around the window manager's {@link OO.ui.WindowManager#isOpened isOpened} method.
+ *
+ * @return {boolean} Window is opened
+ */
+OO.ui.Window.prototype.isOpened = function () {
+ return this.manager.isOpened( this );
+};
+
+/**
+ * Get the window manager.
+ *
+ * All windows must be attached to a window manager, which is used to open
+ * and close the window and control its presentation.
+ *
+ * @return {OO.ui.WindowManager} Manager of window
+ */
+OO.ui.Window.prototype.getManager = function () {
+ return this.manager;
+};
+
+/**
+ * Get the symbolic name of the window size (e.g., `small` or `medium`).
+ *
+ * @return {string} Symbolic name of the size: `small`, `medium`, `large`, `larger`, `full`
+ */
+OO.ui.Window.prototype.getSize = function () {
+ return this.size;
+};
+
+/**
+ * Disable transitions on window's frame for the duration of the callback function, then enable them
+ * back.
+ *
+ * @private
+ * @param {Function} callback Function to call while transitions are disabled
+ */
+OO.ui.Window.prototype.withoutSizeTransitions = function ( callback ) {
+ // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
+ // Disable transitions first, otherwise we'll get values from when the window was animating.
+ var oldTransition,
+ styleObj = this.$frame[ 0 ].style;
+ oldTransition = styleObj.transition || styleObj.OTransition || styleObj.MsTransition ||
+ styleObj.MozTransition || styleObj.WebkitTransition;
+ styleObj.transition = styleObj.OTransition = styleObj.MsTransition =
+ styleObj.MozTransition = styleObj.WebkitTransition = 'none';
+ callback();
+ // Force reflow to make sure the style changes done inside callback really are not transitioned
+ this.$frame.height();
+ styleObj.transition = styleObj.OTransition = styleObj.MsTransition =
+ styleObj.MozTransition = styleObj.WebkitTransition = oldTransition;
+};
+
+/**
+ * Get the height of the full window contents (i.e., the window head, body and foot together).
+ *
+ * What consistitutes the head, body, and foot varies depending on the window type.
+ * A {@link OO.ui.MessageDialog message dialog} displays a title and message in its body,
+ * and any actions in the foot. A {@link OO.ui.ProcessDialog process dialog} displays a title
+ * and special actions in the head, and dialog content in the body.
+ *
+ * To get just the height of the dialog body, use the #getBodyHeight method.
+ *
+ * @return {number} The height of the window contents (the dialog head, body and foot) in pixels
+ */
+OO.ui.Window.prototype.getContentHeight = function () {
+ var bodyHeight,
+ win = this,
+ bodyStyleObj = this.$body[ 0 ].style,
+ frameStyleObj = this.$frame[ 0 ].style;
+
+ // Temporarily resize the frame so getBodyHeight() can use scrollHeight measurements.
+ // Disable transitions first, otherwise we'll get values from when the window was animating.
+ this.withoutSizeTransitions( function () {
+ var oldHeight = frameStyleObj.height,
+ oldPosition = bodyStyleObj.position;
+ frameStyleObj.height = '1px';
+ // Force body to resize to new width
+ bodyStyleObj.position = 'relative';
+ bodyHeight = win.getBodyHeight();
+ frameStyleObj.height = oldHeight;
+ bodyStyleObj.position = oldPosition;
+ } );
+
+ return (
+ // Add buffer for border
+ ( this.$frame.outerHeight() - this.$frame.innerHeight() ) +
+ // Use combined heights of children
+ ( this.$head.outerHeight( true ) + bodyHeight + this.$foot.outerHeight( true ) )
+ );
+};
+
+/**
+ * Get the height of the window body.
+ *
+ * To get the height of the full window contents (the window body, head, and foot together),
+ * use #getContentHeight.
+ *
+ * When this function is called, the window will temporarily have been resized
+ * to height=1px, so .scrollHeight measurements can be taken accurately.
+ *
+ * @return {number} Height of the window body in pixels
+ */
+OO.ui.Window.prototype.getBodyHeight = function () {
+ return this.$body[ 0 ].scrollHeight;
+};
+
+/**
+ * Get the directionality of the frame (right-to-left or left-to-right).
+ *
+ * @return {string} Directionality: `'ltr'` or `'rtl'`
+ */
+OO.ui.Window.prototype.getDir = function () {
+ return this.dir;
+};
+
+/**
+ * Get the 'setup' process.
+ *
+ * The setup process is used to set up a window for use in a particular context,
+ * based on the `data` argument. This method is called during the opening phase of the window’s
+ * lifecycle.
+ *
+ * Override this method to add additional steps to the ‘setup’ process the parent method provides
+ * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
+ * of OO.ui.Process.
+ *
+ * To add window content that persists between openings, you may wish to use the #initialize method
+ * instead.
+ *
+ * @abstract
+ * @param {Object} [data] Window opening data
+ * @return {OO.ui.Process} Setup process
+ */
+OO.ui.Window.prototype.getSetupProcess = function () {
+ return new OO.ui.Process();
+};
+
+/**
+ * Get the ‘ready’ process.
+ *
+ * The ready process is used to ready a window for use in a particular
+ * context, based on the `data` argument. This method is called during the opening phase of
+ * the window’s lifecycle, after the window has been {@link #getSetupProcess setup}.
+ *
+ * Override this method to add additional steps to the ‘ready’ process the parent method
+ * provides using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next}
+ * methods of OO.ui.Process.
+ *
+ * @abstract
+ * @param {Object} [data] Window opening data
+ * @return {OO.ui.Process} Ready process
+ */
+OO.ui.Window.prototype.getReadyProcess = function () {
+ return new OO.ui.Process();
+};
+
+/**
+ * Get the 'hold' process.
+ *
+ * The hold proccess is used to keep a window from being used in a particular context,
+ * based on the `data` argument. This method is called during the closing phase of the window’s
+ * lifecycle.
+ *
+ * Override this method to add additional steps to the 'hold' process the parent method provides
+ * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
+ * of OO.ui.Process.
+ *
+ * @abstract
+ * @param {Object} [data] Window closing data
+ * @return {OO.ui.Process} Hold process
+ */
+OO.ui.Window.prototype.getHoldProcess = function () {
+ return new OO.ui.Process();
+};
+
+/**
+ * Get the ‘teardown’ process.
+ *
+ * The teardown process is used to teardown a window after use. During teardown,
+ * user interactions within the window are conveyed and the window is closed, based on the `data`
+ * argument. This method is called during the closing phase of the window’s lifecycle.
+ *
+ * Override this method to add additional steps to the ‘teardown’ process the parent method provides
+ * using the {@link OO.ui.Process#first first} and {@link OO.ui.Process#next next} methods
+ * of OO.ui.Process.
+ *
+ * @abstract
+ * @param {Object} [data] Window closing data
+ * @return {OO.ui.Process} Teardown process
+ */
+OO.ui.Window.prototype.getTeardownProcess = function () {
+ return new OO.ui.Process();
+};
+
+/**
+ * Set the window manager.
+ *
+ * This will cause the window to initialize. Calling it more than once will cause an error.
+ *
+ * @param {OO.ui.WindowManager} manager Manager for this window
+ * @throws {Error} An error is thrown if the method is called more than once
+ * @chainable
+ */
+OO.ui.Window.prototype.setManager = function ( manager ) {
+ if ( this.manager ) {
+ throw new Error( 'Cannot set window manager, window already has a manager' );
+ }
+
+ this.manager = manager;
+ this.initialize();
+
+ return this;
+};
+
+/**
+ * Set the window size by symbolic name (e.g., 'small' or 'medium')
+ *
+ * @param {string} size Symbolic name of size: `small`, `medium`, `large`, `larger` or
+ * `full`
+ * @chainable
+ */
+OO.ui.Window.prototype.setSize = function ( size ) {
+ this.size = size;
+ this.updateSize();
+ return this;
+};
+
+/**
+ * Update the window size.
+ *
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
+ * @chainable
+ */
+OO.ui.Window.prototype.updateSize = function () {
+ if ( !this.manager ) {
+ throw new Error( 'Cannot update window size, must be attached to a manager' );
+ }
+
+ this.manager.updateWindowSize( this );
+
+ return this;
+};
+
+/**
+ * Set window dimensions. This method is called by the {@link OO.ui.WindowManager window manager}
+ * when the window is opening. In general, setDimensions should not be called directly.
+ *
+ * To set the size of the window, use the #setSize method.
+ *
+ * @param {Object} dim CSS dimension properties
+ * @param {string|number} [dim.width] Width
+ * @param {string|number} [dim.minWidth] Minimum width
+ * @param {string|number} [dim.maxWidth] Maximum width
+ * @param {string|number} [dim.width] Height, omit to set based on height of contents
+ * @param {string|number} [dim.minWidth] Minimum height
+ * @param {string|number} [dim.maxWidth] Maximum height
+ * @chainable
+ */
+OO.ui.Window.prototype.setDimensions = function ( dim ) {
+ var height,
+ win = this,
+ styleObj = this.$frame[ 0 ].style;
+
+ // Calculate the height we need to set using the correct width
+ if ( dim.height === undefined ) {
+ this.withoutSizeTransitions( function () {
+ var oldWidth = styleObj.width;
+ win.$frame.css( 'width', dim.width || '' );
+ height = win.getContentHeight();
+ styleObj.width = oldWidth;
+ } );
+ } else {
+ height = dim.height;
+ }
+
+ this.$frame.css( {
+ width: dim.width || '',
+ minWidth: dim.minWidth || '',
+ maxWidth: dim.maxWidth || '',
+ height: height || '',
+ minHeight: dim.minHeight || '',
+ maxHeight: dim.maxHeight || ''
+ } );
+
+ return this;
+};
+
+/**
+ * Initialize window contents.
+ *
+ * Before the window is opened for the first time, #initialize is called so that content that
+ * persists between openings can be added to the window.
+ *
+ * To set up a window with new content each time the window opens, use #getSetupProcess.
+ *
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
+ * @chainable
+ */
+OO.ui.Window.prototype.initialize = function () {
+ if ( !this.manager ) {
+ throw new Error( 'Cannot initialize window, must be attached to a manager' );
+ }
+
+ // Properties
+ this.$head = $( '<div>' );
+ this.$body = $( '<div>' );
+ this.$foot = $( '<div>' );
+ this.dir = OO.ui.Element.static.getDir( this.$content ) || 'ltr';
+ this.$document = $( this.getElementDocument() );
+
+ // Events
+ this.$element.on( 'mousedown', this.onMouseDown.bind( this ) );
+
+ // Initialization
+ this.$head.addClass( 'oo-ui-window-head' );
+ this.$body.addClass( 'oo-ui-window-body' );
+ this.$foot.addClass( 'oo-ui-window-foot' );
+ this.$content.append( this.$head, this.$body, this.$foot );
+
+ return this;
+};
+
+/**
+ * Open the window.
+ *
+ * This method is a wrapper around a call to the window manager’s {@link OO.ui.WindowManager#openWindow openWindow}
+ * method, which returns a promise resolved when the window is done opening.
+ *
+ * To customize the window each time it opens, use #getSetupProcess or #getReadyProcess.
+ *
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} Promise resolved with a value when the window is opened, or rejected
+ * if the window fails to open. When the promise is resolved successfully, the first argument of the
+ * value is a new promise, which is resolved when the window begins closing.
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
+ */
+OO.ui.Window.prototype.open = function ( data ) {
+ if ( !this.manager ) {
+ throw new Error( 'Cannot open window, must be attached to a manager' );
+ }
+
+ return this.manager.openWindow( this, data );
+};
+
+/**
+ * Close the window.
+ *
+ * This method is a wrapper around a call to the window
+ * manager’s {@link OO.ui.WindowManager#closeWindow closeWindow} method,
+ * which returns a closing promise resolved when the window is done closing.
+ *
+ * The window's #getHoldProcess and #getTeardownProcess methods are called during the closing
+ * phase of the window’s lifecycle and can be used to specify closing behavior each time
+ * the window closes.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} Promise resolved when window is closed
+ * @throws {Error} An error is thrown if the window is not attached to a window manager
+ */
+OO.ui.Window.prototype.close = function ( data ) {
+ if ( !this.manager ) {
+ throw new Error( 'Cannot close window, must be attached to a manager' );
+ }
+
+ return this.manager.closeWindow( this, data );
+};
+
+/**
+ * Setup window.
+ *
+ * This is called by OO.ui.WindowManager during window opening, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} Promise resolved when window is setup
+ */
+OO.ui.Window.prototype.setup = function ( data ) {
+ var win = this,
+ deferred = $.Deferred();
+
+ this.toggle( true );
+
+ this.getSetupProcess( data ).execute().done( function () {
+ // Force redraw by asking the browser to measure the elements' widths
+ win.$element.addClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
+ win.$content.addClass( 'oo-ui-window-content-setup' ).width();
+ deferred.resolve();
+ } );
+
+ return deferred.promise();
+};
+
+/**
+ * Ready window.
+ *
+ * This is called by OO.ui.WindowManager during window opening, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} Promise resolved when window is ready
+ */
+OO.ui.Window.prototype.ready = function ( data ) {
+ var win = this,
+ deferred = $.Deferred();
+
+ this.$content.focus();
+ this.getReadyProcess( data ).execute().done( function () {
+ // Force redraw by asking the browser to measure the elements' widths
+ win.$element.addClass( 'oo-ui-window-ready' ).width();
+ win.$content.addClass( 'oo-ui-window-content-ready' ).width();
+ deferred.resolve();
+ } );
+
+ return deferred.promise();
+};
+
+/**
+ * Hold window.
+ *
+ * This is called by OO.ui.WindowManager during window closing, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} Promise resolved when window is held
+ */
+OO.ui.Window.prototype.hold = function ( data ) {
+ var win = this,
+ deferred = $.Deferred();
+
+ this.getHoldProcess( data ).execute().done( function () {
+ // Get the focused element within the window's content
+ var $focus = win.$content.find( OO.ui.Element.static.getDocument( win.$content ).activeElement );
+
+ // Blur the focused element
+ if ( $focus.length ) {
+ $focus[ 0 ].blur();
+ }
+
+ // Force redraw by asking the browser to measure the elements' widths
+ win.$element.removeClass( 'oo-ui-window-ready' ).width();
+ win.$content.removeClass( 'oo-ui-window-content-ready' ).width();
+ deferred.resolve();
+ } );
+
+ return deferred.promise();
+};
+
+/**
+ * Teardown window.
+ *
+ * This is called by OO.ui.WindowManager during window closing, and should not be called directly
+ * by other systems.
+ *
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} Promise resolved when window is torn down
+ */
+OO.ui.Window.prototype.teardown = function ( data ) {
+ var win = this;
+
+ return this.getTeardownProcess( data ).execute()
+ .done( function () {
+ // Force redraw by asking the browser to measure the elements' widths
+ win.$element.removeClass( 'oo-ui-window-active oo-ui-window-setup' ).width();
+ win.$content.removeClass( 'oo-ui-window-content-setup' ).width();
+ win.toggle( false );
+ } );
+};
diff --git a/vendor/oojs/oojs-ui/src/WindowManager.js b/vendor/oojs/oojs-ui/src/WindowManager.js
new file mode 100644
index 00000000..4731011a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/WindowManager.js
@@ -0,0 +1,675 @@
+/**
+ * Window managers are used to open and close {@link OO.ui.Window windows} and control their presentation.
+ * Managed windows are mutually exclusive. If a new window is opened while a current window is opening
+ * or is opened, the current window will be closed and any ongoing {@link OO.ui.Process process} will be cancelled. Windows
+ * themselves are persistent and—rather than being torn down when closed—can be repopulated with the
+ * pertinent data and reused.
+ *
+ * Over the lifecycle of a window, the window manager makes available three promises: `opening`,
+ * `opened`, and `closing`, which represent the primary stages of the cycle:
+ *
+ * **Opening**: the opening stage begins when the window manager’s #openWindow or a window’s
+ * {@link OO.ui.Window#open open} method is used, and the window manager begins to open the window.
+ *
+ * - an `opening` event is emitted with an `opening` promise
+ * - the #getSetupDelay method is called and the returned value is used to time a pause in execution before
+ * the window’s {@link OO.ui.Window#getSetupProcess getSetupProcess} method is called on the
+ * window and its result executed
+ * - a `setup` progress notification is emitted from the `opening` promise
+ * - the #getReadyDelay method is called the returned value is used to time a pause in execution before
+ * the window’s {@link OO.ui.Window#getReadyProcess getReadyProcess} method is called on the
+ * window and its result executed
+ * - a `ready` progress notification is emitted from the `opening` promise
+ * - the `opening` promise is resolved with an `opened` promise
+ *
+ * **Opened**: the window is now open.
+ *
+ * **Closing**: the closing stage begins when the window manager's #closeWindow or the
+ * window's {@link OO.ui.Window#close close} methods is used, and the window manager begins
+ * to close the window.
+ *
+ * - the `opened` promise is resolved with `closing` promise and a `closing` event is emitted
+ * - the #getHoldDelay method is called and the returned value is used to time a pause in execution before
+ * the window's {@link OO.ui.Window#getHoldProcess getHoldProces} method is called on the
+ * window and its result executed
+ * - a `hold` progress notification is emitted from the `closing` promise
+ * - the #getTeardownDelay() method is called and the returned value is used to time a pause in execution before
+ * the window's {@link OO.ui.Window#getTeardownProcess getTeardownProcess} method is called on the
+ * window and its result executed
+ * - a `teardown` progress notification is emitted from the `closing` promise
+ * - the `closing` promise is resolved. The window is now closed
+ *
+ * See the [OOjs UI documentation on MediaWiki][1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ *
+ * @class
+ * @extends OO.ui.Element
+ * @mixins OO.EventEmitter
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.Factory} [factory] Window factory to use for automatic instantiation
+ * Note that window classes that are instantiated with a factory must have
+ * a {@link OO.ui.Dialog#static-name static name} property that specifies a symbolic name.
+ * @cfg {boolean} [modal=true] Prevent interaction outside the dialog
+ */
+OO.ui.WindowManager = function OoUiWindowManager( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.WindowManager.super.call( this, config );
+
+ // Mixin constructors
+ OO.EventEmitter.call( this );
+
+ // Properties
+ this.factory = config.factory;
+ this.modal = config.modal === undefined || !!config.modal;
+ this.windows = {};
+ this.opening = null;
+ this.opened = null;
+ this.closing = null;
+ this.preparingToOpen = null;
+ this.preparingToClose = null;
+ this.currentWindow = null;
+ this.globalEvents = false;
+ this.$ariaHidden = null;
+ this.onWindowResizeTimeout = null;
+ this.onWindowResizeHandler = this.onWindowResize.bind( this );
+ this.afterWindowResizeHandler = this.afterWindowResize.bind( this );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-windowManager' )
+ .toggleClass( 'oo-ui-windowManager-modal', this.modal );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.WindowManager, OO.ui.Element );
+OO.mixinClass( OO.ui.WindowManager, OO.EventEmitter );
+
+/* Events */
+
+/**
+ * An 'opening' event is emitted when the window begins to be opened.
+ *
+ * @event opening
+ * @param {OO.ui.Window} win Window that's being opened
+ * @param {jQuery.Promise} opening An `opening` promise resolved with a value when the window is opened successfully.
+ * When the `opening` promise is resolved, the first argument of the value is an 'opened' promise, the second argument
+ * is the opening data. The `opening` promise emits `setup` and `ready` notifications when those processes are complete.
+ * @param {Object} data Window opening data
+ */
+
+/**
+ * A 'closing' event is emitted when the window begins to be closed.
+ *
+ * @event closing
+ * @param {OO.ui.Window} win Window that's being closed
+ * @param {jQuery.Promise} closing A `closing` promise is resolved with a value when the window
+ * is closed successfully. The promise emits `hold` and `teardown` notifications when those
+ * processes are complete. When the `closing` promise is resolved, the first argument of its value
+ * is the closing data.
+ * @param {Object} data Window closing data
+ */
+
+/**
+ * A 'resize' event is emitted when a window is resized.
+ *
+ * @event resize
+ * @param {OO.ui.Window} win Window that was resized
+ */
+
+/* Static Properties */
+
+/**
+ * Map of the symbolic name of each window size and its CSS properties.
+ *
+ * @static
+ * @inheritable
+ * @property {Object}
+ */
+OO.ui.WindowManager.static.sizes = {
+ small: {
+ width: 300
+ },
+ medium: {
+ width: 500
+ },
+ large: {
+ width: 700
+ },
+ larger: {
+ width: 900
+ },
+ full: {
+ // These can be non-numeric because they are never used in calculations
+ width: '100%',
+ height: '100%'
+ }
+};
+
+/**
+ * Symbolic name of the default window size.
+ *
+ * The default size is used if the window's requested size is not recognized.
+ *
+ * @static
+ * @inheritable
+ * @property {string}
+ */
+OO.ui.WindowManager.static.defaultSize = 'medium';
+
+/* Methods */
+
+/**
+ * Handle window resize events.
+ *
+ * @private
+ * @param {jQuery.Event} e Window resize event
+ */
+OO.ui.WindowManager.prototype.onWindowResize = function () {
+ clearTimeout( this.onWindowResizeTimeout );
+ this.onWindowResizeTimeout = setTimeout( this.afterWindowResizeHandler, 200 );
+};
+
+/**
+ * Handle window resize events.
+ *
+ * @private
+ * @param {jQuery.Event} e Window resize event
+ */
+OO.ui.WindowManager.prototype.afterWindowResize = function () {
+ if ( this.currentWindow ) {
+ this.updateWindowSize( this.currentWindow );
+ }
+};
+
+/**
+ * Check if window is opening.
+ *
+ * @return {boolean} Window is opening
+ */
+OO.ui.WindowManager.prototype.isOpening = function ( win ) {
+ return win === this.currentWindow && !!this.opening && this.opening.state() === 'pending';
+};
+
+/**
+ * Check if window is closing.
+ *
+ * @return {boolean} Window is closing
+ */
+OO.ui.WindowManager.prototype.isClosing = function ( win ) {
+ return win === this.currentWindow && !!this.closing && this.closing.state() === 'pending';
+};
+
+/**
+ * Check if window is opened.
+ *
+ * @return {boolean} Window is opened
+ */
+OO.ui.WindowManager.prototype.isOpened = function ( win ) {
+ return win === this.currentWindow && !!this.opened && this.opened.state() === 'pending';
+};
+
+/**
+ * Check if a window is being managed.
+ *
+ * @param {OO.ui.Window} win Window to check
+ * @return {boolean} Window is being managed
+ */
+OO.ui.WindowManager.prototype.hasWindow = function ( win ) {
+ var name;
+
+ for ( name in this.windows ) {
+ if ( this.windows[ name ] === win ) {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Get the number of milliseconds to wait after opening begins before executing the ‘setup’ process.
+ *
+ * @param {OO.ui.Window} win Window being opened
+ * @param {Object} [data] Window opening data
+ * @return {number} Milliseconds to wait
+ */
+OO.ui.WindowManager.prototype.getSetupDelay = function () {
+ return 0;
+};
+
+/**
+ * Get the number of milliseconds to wait after setup has finished before executing the ‘ready’ process.
+ *
+ * @param {OO.ui.Window} win Window being opened
+ * @param {Object} [data] Window opening data
+ * @return {number} Milliseconds to wait
+ */
+OO.ui.WindowManager.prototype.getReadyDelay = function () {
+ return 0;
+};
+
+/**
+ * Get the number of milliseconds to wait after closing has begun before executing the 'hold' process.
+ *
+ * @param {OO.ui.Window} win Window being closed
+ * @param {Object} [data] Window closing data
+ * @return {number} Milliseconds to wait
+ */
+OO.ui.WindowManager.prototype.getHoldDelay = function () {
+ return 0;
+};
+
+/**
+ * Get the number of milliseconds to wait after the ‘hold’ process has finished before
+ * executing the ‘teardown’ process.
+ *
+ * @param {OO.ui.Window} win Window being closed
+ * @param {Object} [data] Window closing data
+ * @return {number} Milliseconds to wait
+ */
+OO.ui.WindowManager.prototype.getTeardownDelay = function () {
+ return this.modal ? 250 : 0;
+};
+
+/**
+ * Get a window by its symbolic name.
+ *
+ * If the window is not yet instantiated and its symbolic name is recognized by a factory, it will be
+ * instantiated and added to the window manager automatically. Please see the [OOjs UI documentation on MediaWiki][3]
+ * for more information about using factories.
+ * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ *
+ * @param {string} name Symbolic name of the window
+ * @return {jQuery.Promise} Promise resolved with matching window, or rejected with an OO.ui.Error
+ * @throws {Error} An error is thrown if the symbolic name is not recognized by the factory.
+ * @throws {Error} An error is thrown if the named window is not recognized as a managed window.
+ */
+OO.ui.WindowManager.prototype.getWindow = function ( name ) {
+ var deferred = $.Deferred(),
+ win = this.windows[ name ];
+
+ if ( !( win instanceof OO.ui.Window ) ) {
+ if ( this.factory ) {
+ if ( !this.factory.lookup( name ) ) {
+ deferred.reject( new OO.ui.Error(
+ 'Cannot auto-instantiate window: symbolic name is unrecognized by the factory'
+ ) );
+ } else {
+ win = this.factory.create( name );
+ this.addWindows( [ win ] );
+ deferred.resolve( win );
+ }
+ } else {
+ deferred.reject( new OO.ui.Error(
+ 'Cannot get unmanaged window: symbolic name unrecognized as a managed window'
+ ) );
+ }
+ } else {
+ deferred.resolve( win );
+ }
+
+ return deferred.promise();
+};
+
+/**
+ * Get current window.
+ *
+ * @return {OO.ui.Window|null} Currently opening/opened/closing window
+ */
+OO.ui.WindowManager.prototype.getCurrentWindow = function () {
+ return this.currentWindow;
+};
+
+/**
+ * Open a window.
+ *
+ * @param {OO.ui.Window|string} win Window object or symbolic name of window to open
+ * @param {Object} [data] Window opening data
+ * @return {jQuery.Promise} An `opening` promise resolved when the window is done opening.
+ * See {@link #event-opening 'opening' event} for more information about `opening` promises.
+ * @fires opening
+ */
+OO.ui.WindowManager.prototype.openWindow = function ( win, data ) {
+ var manager = this,
+ opening = $.Deferred();
+
+ // Argument handling
+ if ( typeof win === 'string' ) {
+ return this.getWindow( win ).then( function ( win ) {
+ return manager.openWindow( win, data );
+ } );
+ }
+
+ // Error handling
+ if ( !this.hasWindow( win ) ) {
+ opening.reject( new OO.ui.Error(
+ 'Cannot open window: window is not attached to manager'
+ ) );
+ } else if ( this.preparingToOpen || this.opening || this.opened ) {
+ opening.reject( new OO.ui.Error(
+ 'Cannot open window: another window is opening or open'
+ ) );
+ }
+
+ // Window opening
+ if ( opening.state() !== 'rejected' ) {
+ // If a window is currently closing, wait for it to complete
+ this.preparingToOpen = $.when( this.closing );
+ // Ensure handlers get called after preparingToOpen is set
+ this.preparingToOpen.done( function () {
+ if ( manager.modal ) {
+ manager.toggleGlobalEvents( true );
+ manager.toggleAriaIsolation( true );
+ }
+ manager.currentWindow = win;
+ manager.opening = opening;
+ manager.preparingToOpen = null;
+ manager.emit( 'opening', win, opening, data );
+ setTimeout( function () {
+ win.setup( data ).then( function () {
+ manager.updateWindowSize( win );
+ manager.opening.notify( { state: 'setup' } );
+ setTimeout( function () {
+ win.ready( data ).then( function () {
+ manager.opening.notify( { state: 'ready' } );
+ manager.opening = null;
+ manager.opened = $.Deferred();
+ opening.resolve( manager.opened.promise(), data );
+ } );
+ }, manager.getReadyDelay() );
+ } );
+ }, manager.getSetupDelay() );
+ } );
+ }
+
+ return opening.promise();
+};
+
+/**
+ * Close a window.
+ *
+ * @param {OO.ui.Window|string} win Window object or symbolic name of window to close
+ * @param {Object} [data] Window closing data
+ * @return {jQuery.Promise} A `closing` promise resolved when the window is done closing.
+ * See {@link #event-closing 'closing' event} for more information about closing promises.
+ * @throws {Error} An error is thrown if the window is not managed by the window manager.
+ * @fires closing
+ */
+OO.ui.WindowManager.prototype.closeWindow = function ( win, data ) {
+ var manager = this,
+ closing = $.Deferred(),
+ opened;
+
+ // Argument handling
+ if ( typeof win === 'string' ) {
+ win = this.windows[ win ];
+ } else if ( !this.hasWindow( win ) ) {
+ win = null;
+ }
+
+ // Error handling
+ if ( !win ) {
+ closing.reject( new OO.ui.Error(
+ 'Cannot close window: window is not attached to manager'
+ ) );
+ } else if ( win !== this.currentWindow ) {
+ closing.reject( new OO.ui.Error(
+ 'Cannot close window: window already closed with different data'
+ ) );
+ } else if ( this.preparingToClose || this.closing ) {
+ closing.reject( new OO.ui.Error(
+ 'Cannot close window: window already closing with different data'
+ ) );
+ }
+
+ // Window closing
+ if ( closing.state() !== 'rejected' ) {
+ // If the window is currently opening, close it when it's done
+ this.preparingToClose = $.when( this.opening );
+ // Ensure handlers get called after preparingToClose is set
+ this.preparingToClose.done( function () {
+ manager.closing = closing;
+ manager.preparingToClose = null;
+ manager.emit( 'closing', win, closing, data );
+ opened = manager.opened;
+ manager.opened = null;
+ opened.resolve( closing.promise(), data );
+ setTimeout( function () {
+ win.hold( data ).then( function () {
+ closing.notify( { state: 'hold' } );
+ setTimeout( function () {
+ win.teardown( data ).then( function () {
+ closing.notify( { state: 'teardown' } );
+ if ( manager.modal ) {
+ manager.toggleGlobalEvents( false );
+ manager.toggleAriaIsolation( false );
+ }
+ manager.closing = null;
+ manager.currentWindow = null;
+ closing.resolve( data );
+ } );
+ }, manager.getTeardownDelay() );
+ } );
+ }, manager.getHoldDelay() );
+ } );
+ }
+
+ return closing.promise();
+};
+
+/**
+ * Add windows to the window manager.
+ *
+ * Windows can be added by reference, symbolic name, or explicitly defined symbolic names.
+ * See the [OOjs ui documentation on MediaWiki] [2] for examples.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Window_managers
+ *
+ * @param {Object.<string,OO.ui.Window>|OO.ui.Window[]} windows An array of window objects specified
+ * by reference, symbolic name, or explicitly defined symbolic names.
+ * @throws {Error} An error is thrown if a window is added by symbolic name, but has neither an
+ * explicit nor a statically configured symbolic name.
+ */
+OO.ui.WindowManager.prototype.addWindows = function ( windows ) {
+ var i, len, win, name, list;
+
+ if ( Array.isArray( windows ) ) {
+ // Convert to map of windows by looking up symbolic names from static configuration
+ list = {};
+ for ( i = 0, len = windows.length; i < len; i++ ) {
+ name = windows[ i ].constructor.static.name;
+ if ( typeof name !== 'string' ) {
+ throw new Error( 'Cannot add window' );
+ }
+ list[ name ] = windows[ i ];
+ }
+ } else if ( OO.isPlainObject( windows ) ) {
+ list = windows;
+ }
+
+ // Add windows
+ for ( name in list ) {
+ win = list[ name ];
+ this.windows[ name ] = win.toggle( false );
+ this.$element.append( win.$element );
+ win.setManager( this );
+ }
+};
+
+/**
+ * Remove the specified windows from the windows manager.
+ *
+ * Windows will be closed before they are removed. If you wish to remove all windows, you may wish to use
+ * the #clearWindows method instead. If you no longer need the window manager and want to ensure that it no
+ * longer listens to events, use the #destroy method.
+ *
+ * @param {string[]} names Symbolic names of windows to remove
+ * @return {jQuery.Promise} Promise resolved when window is closed and removed
+ * @throws {Error} An error is thrown if the named windows are not managed by the window manager.
+ */
+OO.ui.WindowManager.prototype.removeWindows = function ( names ) {
+ var i, len, win, name, cleanupWindow,
+ manager = this,
+ promises = [],
+ cleanup = function ( name, win ) {
+ delete manager.windows[ name ];
+ win.$element.detach();
+ };
+
+ for ( i = 0, len = names.length; i < len; i++ ) {
+ name = names[ i ];
+ win = this.windows[ name ];
+ if ( !win ) {
+ throw new Error( 'Cannot remove window' );
+ }
+ cleanupWindow = cleanup.bind( null, name, win );
+ promises.push( this.closeWindow( name ).then( cleanupWindow, cleanupWindow ) );
+ }
+
+ return $.when.apply( $, promises );
+};
+
+/**
+ * Remove all windows from the window manager.
+ *
+ * Windows will be closed before they are removed. Note that the window manager, though not in use, will still
+ * listen to events. If the window manager will not be used again, you may wish to use the #destroy method instead.
+ * To remove just a subset of windows, use the #removeWindows method.
+ *
+ * @return {jQuery.Promise} Promise resolved when all windows are closed and removed
+ */
+OO.ui.WindowManager.prototype.clearWindows = function () {
+ return this.removeWindows( Object.keys( this.windows ) );
+};
+
+/**
+ * Set dialog size. In general, this method should not be called directly.
+ *
+ * Fullscreen mode will be used if the dialog is too wide to fit in the screen.
+ *
+ * @chainable
+ */
+OO.ui.WindowManager.prototype.updateWindowSize = function ( win ) {
+ // Bypass for non-current, and thus invisible, windows
+ if ( win !== this.currentWindow ) {
+ return;
+ }
+
+ var viewport = OO.ui.Element.static.getDimensions( win.getElementWindow() ),
+ sizes = this.constructor.static.sizes,
+ size = win.getSize();
+
+ if ( !sizes[ size ] ) {
+ size = this.constructor.static.defaultSize;
+ }
+ if ( size !== 'full' && viewport.rect.right - viewport.rect.left < sizes[ size ].width ) {
+ size = 'full';
+ }
+
+ this.$element.toggleClass( 'oo-ui-windowManager-fullscreen', size === 'full' );
+ this.$element.toggleClass( 'oo-ui-windowManager-floating', size !== 'full' );
+ win.setDimensions( sizes[ size ] );
+
+ this.emit( 'resize', win );
+
+ return this;
+};
+
+/**
+ * Bind or unbind global events for scrolling.
+ *
+ * @private
+ * @param {boolean} [on] Bind global events
+ * @chainable
+ */
+OO.ui.WindowManager.prototype.toggleGlobalEvents = function ( on ) {
+ on = on === undefined ? !!this.globalEvents : !!on;
+
+ var scrollWidth, bodyMargin,
+ $body = $( this.getElementDocument().body ),
+ // We could have multiple window managers open so only modify
+ // the body css at the bottom of the stack
+ stackDepth = $body.data( 'windowManagerGlobalEvents' ) || 0 ;
+
+ if ( on ) {
+ if ( !this.globalEvents ) {
+ $( this.getElementWindow() ).on( {
+ // Start listening for top-level window dimension changes
+ 'orientationchange resize': this.onWindowResizeHandler
+ } );
+ if ( stackDepth === 0 ) {
+ scrollWidth = window.innerWidth - document.documentElement.clientWidth;
+ bodyMargin = parseFloat( $body.css( 'margin-right' ) ) || 0;
+ $body.css( {
+ overflow: 'hidden',
+ 'margin-right': bodyMargin + scrollWidth
+ } );
+ }
+ stackDepth++;
+ this.globalEvents = true;
+ }
+ } else if ( this.globalEvents ) {
+ $( this.getElementWindow() ).off( {
+ // Stop listening for top-level window dimension changes
+ 'orientationchange resize': this.onWindowResizeHandler
+ } );
+ stackDepth--;
+ if ( stackDepth === 0 ) {
+ $body.css( {
+ overflow: '',
+ 'margin-right': ''
+ } );
+ }
+ this.globalEvents = false;
+ }
+ $body.data( 'windowManagerGlobalEvents', stackDepth );
+
+ return this;
+};
+
+/**
+ * Toggle screen reader visibility of content other than the window manager.
+ *
+ * @private
+ * @param {boolean} [isolate] Make only the window manager visible to screen readers
+ * @chainable
+ */
+OO.ui.WindowManager.prototype.toggleAriaIsolation = function ( isolate ) {
+ isolate = isolate === undefined ? !this.$ariaHidden : !!isolate;
+
+ if ( isolate ) {
+ if ( !this.$ariaHidden ) {
+ // Hide everything other than the window manager from screen readers
+ this.$ariaHidden = $( 'body' )
+ .children()
+ .not( this.$element.parentsUntil( 'body' ).last() )
+ .attr( 'aria-hidden', '' );
+ }
+ } else if ( this.$ariaHidden ) {
+ // Restore screen reader visibility
+ this.$ariaHidden.removeAttr( 'aria-hidden' );
+ this.$ariaHidden = null;
+ }
+
+ return this;
+};
+
+/**
+ * Destroy the window manager.
+ *
+ * Destroying the window manager ensures that it will no longer listen to events. If you would like to
+ * continue using the window manager, but wish to remove all windows from it, use the #clearWindows method
+ * instead.
+ */
+OO.ui.WindowManager.prototype.destroy = function () {
+ this.toggleGlobalEvents( false );
+ this.toggleAriaIsolation( false );
+ this.clearWindows();
+ this.$element.remove();
+};
diff --git a/vendor/oojs/oojs-ui/src/core.js b/vendor/oojs/oojs-ui/src/core.js
new file mode 100644
index 00000000..1331f5cc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/core.js
@@ -0,0 +1,286 @@
+/**
+ * Namespace for all classes, static methods and static properties.
+ *
+ * @class
+ * @singleton
+ */
+OO.ui = {};
+
+OO.ui.bind = $.proxy;
+
+/**
+ * @property {Object}
+ */
+OO.ui.Keys = {
+ UNDEFINED: 0,
+ BACKSPACE: 8,
+ DELETE: 46,
+ LEFT: 37,
+ RIGHT: 39,
+ UP: 38,
+ DOWN: 40,
+ ENTER: 13,
+ END: 35,
+ HOME: 36,
+ TAB: 9,
+ PAGEUP: 33,
+ PAGEDOWN: 34,
+ ESCAPE: 27,
+ SHIFT: 16,
+ SPACE: 32
+};
+
+/**
+ * Check if an element is focusable.
+ * Inspired from :focusable in jQueryUI v1.11.4 - 2015-04-14
+ *
+ * @param {jQuery} element Element to test
+ * @return {Boolean} [description]
+ */
+OO.ui.isFocusableElement = function ( $element ) {
+ var node = $element[0],
+ nodeName = node.nodeName.toLowerCase(),
+ // Check if the element have tabindex set
+ isInElementGroup = /^(input|select|textarea|button|object)$/.test( nodeName ),
+ // Check if the element is a link with href or if it has tabindex
+ isOtherElement = (
+ ( nodeName === 'a' && node.href ) ||
+ !isNaN( $element.attr( 'tabindex' ) )
+ ),
+ // Check if the element is visible
+ isVisible = (
+ // This is quicker than calling $element.is( ':visible' )
+ $.expr.filters.visible( node ) &&
+ // Check that all parents are visible
+ !$element.parents().addBack().filter( function () {
+ return $.css( this, 'visibility' ) === 'hidden';
+ } ).length
+ );
+
+ return (
+ ( isInElementGroup ? !node.disabled : isOtherElement ) &&
+ isVisible
+ );
+};
+
+/**
+ * Get the user's language and any fallback languages.
+ *
+ * These language codes are used to localize user interface elements in the user's language.
+ *
+ * In environments that provide a localization system, this function should be overridden to
+ * return the user's language(s). The default implementation returns English (en) only.
+ *
+ * @return {string[]} Language codes, in descending order of priority
+ */
+OO.ui.getUserLanguages = function () {
+ return [ 'en' ];
+};
+
+/**
+ * Get a value in an object keyed by language code.
+ *
+ * @param {Object.<string,Mixed>} obj Object keyed by language code
+ * @param {string|null} [lang] Language code, if omitted or null defaults to any user language
+ * @param {string} [fallback] Fallback code, used if no matching language can be found
+ * @return {Mixed} Local value
+ */
+OO.ui.getLocalValue = function ( obj, lang, fallback ) {
+ var i, len, langs;
+
+ // Requested language
+ if ( obj[ lang ] ) {
+ return obj[ lang ];
+ }
+ // Known user language
+ langs = OO.ui.getUserLanguages();
+ for ( i = 0, len = langs.length; i < len; i++ ) {
+ lang = langs[ i ];
+ if ( obj[ lang ] ) {
+ return obj[ lang ];
+ }
+ }
+ // Fallback language
+ if ( obj[ fallback ] ) {
+ return obj[ fallback ];
+ }
+ // First existing language
+ for ( lang in obj ) {
+ return obj[ lang ];
+ }
+
+ return undefined;
+};
+
+/**
+ * Check if a node is contained within another node
+ *
+ * Similar to jQuery#contains except a list of containers can be supplied
+ * and a boolean argument allows you to include the container in the match list
+ *
+ * @param {HTMLElement|HTMLElement[]} containers Container node(s) to search in
+ * @param {HTMLElement} contained Node to find
+ * @param {boolean} [matchContainers] Include the container(s) in the list of nodes to match, otherwise only match descendants
+ * @return {boolean} The node is in the list of target nodes
+ */
+OO.ui.contains = function ( containers, contained, matchContainers ) {
+ var i;
+ if ( !Array.isArray( containers ) ) {
+ containers = [ containers ];
+ }
+ for ( i = containers.length - 1; i >= 0; i-- ) {
+ if ( ( matchContainers && contained === containers[ i ] ) || $.contains( containers[ i ], contained ) ) {
+ return true;
+ }
+ }
+ return false;
+};
+
+/**
+ * Return a function, that, as long as it continues to be invoked, will not
+ * be triggered. The function will be called after it stops being called for
+ * N milliseconds. If `immediate` is passed, trigger the function on the
+ * leading edge, instead of the trailing.
+ *
+ * Ported from: http://underscorejs.org/underscore.js
+ *
+ * @param {Function} func
+ * @param {number} wait
+ * @param {boolean} immediate
+ * @return {Function}
+ */
+OO.ui.debounce = function ( func, wait, immediate ) {
+ var timeout;
+ return function () {
+ var context = this,
+ args = arguments,
+ later = function () {
+ timeout = null;
+ if ( !immediate ) {
+ func.apply( context, args );
+ }
+ };
+ if ( immediate && !timeout ) {
+ func.apply( context, args );
+ }
+ clearTimeout( timeout );
+ timeout = setTimeout( later, wait );
+ };
+};
+
+/**
+ * Reconstitute a JavaScript object corresponding to a widget created by
+ * the PHP implementation.
+ *
+ * This is an alias for `OO.ui.Element.static.infuse()`.
+ *
+ * @param {string|HTMLElement|jQuery} idOrNode
+ * A DOM id (if a string) or node for the widget to infuse.
+ * @return {OO.ui.Element}
+ * The `OO.ui.Element` corresponding to this (infusable) document node.
+ */
+OO.ui.infuse = function ( idOrNode ) {
+ return OO.ui.Element.static.infuse( idOrNode );
+};
+
+( function () {
+ /**
+ * Message store for the default implementation of OO.ui.msg
+ *
+ * Environments that provide a localization system should not use this, but should override
+ * OO.ui.msg altogether.
+ *
+ * @private
+ */
+ var messages = {
+ // Tool tip for a button that moves items in a list down one place
+ 'ooui-outline-control-move-down': 'Move item down',
+ // Tool tip for a button that moves items in a list up one place
+ 'ooui-outline-control-move-up': 'Move item up',
+ // Tool tip for a button that removes items from a list
+ 'ooui-outline-control-remove': 'Remove item',
+ // Label for the toolbar group that contains a list of all other available tools
+ 'ooui-toolbar-more': 'More',
+ // Label for the fake tool that expands the full list of tools in a toolbar group
+ 'ooui-toolgroup-expand': 'More',
+ // Label for the fake tool that collapses the full list of tools in a toolbar group
+ 'ooui-toolgroup-collapse': 'Fewer',
+ // Default label for the accept button of a confirmation dialog
+ 'ooui-dialog-message-accept': 'OK',
+ // Default label for the reject button of a confirmation dialog
+ 'ooui-dialog-message-reject': 'Cancel',
+ // Title for process dialog error description
+ 'ooui-dialog-process-error': 'Something went wrong',
+ // Label for process dialog dismiss error button, visible when describing errors
+ 'ooui-dialog-process-dismiss': 'Dismiss',
+ // Label for process dialog retry action button, visible when describing only recoverable errors
+ 'ooui-dialog-process-retry': 'Try again',
+ // Label for process dialog retry action button, visible when describing only warnings
+ 'ooui-dialog-process-continue': 'Continue'
+ };
+
+ /**
+ * Get a localized message.
+ *
+ * In environments that provide a localization system, this function should be overridden to
+ * return the message translated in the user's language. The default implementation always returns
+ * English messages.
+ *
+ * After the message key, message parameters may optionally be passed. In the default implementation,
+ * any occurrences of $1 are replaced with the first parameter, $2 with the second parameter, etc.
+ * Alternative implementations of OO.ui.msg may use any substitution system they like, as long as
+ * they support unnamed, ordered message parameters.
+ *
+ * @abstract
+ * @param {string} key Message key
+ * @param {Mixed...} [params] Message parameters
+ * @return {string} Translated message with parameters substituted
+ */
+ OO.ui.msg = function ( key ) {
+ var message = messages[ key ],
+ params = Array.prototype.slice.call( arguments, 1 );
+ if ( typeof message === 'string' ) {
+ // Perform $1 substitution
+ message = message.replace( /\$(\d+)/g, function ( unused, n ) {
+ var i = parseInt( n, 10 );
+ return params[ i - 1 ] !== undefined ? params[ i - 1 ] : '$' + n;
+ } );
+ } else {
+ // Return placeholder if message not found
+ message = '[' + key + ']';
+ }
+ return message;
+ };
+
+ /**
+ * Package a message and arguments for deferred resolution.
+ *
+ * Use this when you are statically specifying a message and the message may not yet be present.
+ *
+ * @param {string} key Message key
+ * @param {Mixed...} [params] Message parameters
+ * @return {Function} Function that returns the resolved message when executed
+ */
+ OO.ui.deferMsg = function () {
+ var args = arguments;
+ return function () {
+ return OO.ui.msg.apply( OO.ui, args );
+ };
+ };
+
+ /**
+ * Resolve a message.
+ *
+ * If the message is a function it will be executed, otherwise it will pass through directly.
+ *
+ * @param {Function|string} msg Deferred message, or message text
+ * @return {string} Resolved message
+ */
+ OO.ui.resolveMsg = function ( msg ) {
+ if ( $.isFunction( msg ) ) {
+ return msg();
+ }
+ return msg;
+ };
+
+} )();
diff --git a/vendor/oojs/oojs-ui/src/dialogs/MessageDialog.js b/vendor/oojs/oojs-ui/src/dialogs/MessageDialog.js
new file mode 100644
index 00000000..4ec12615
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/dialogs/MessageDialog.js
@@ -0,0 +1,325 @@
+/**
+ * MessageDialogs display a confirmation or alert message. By default, the rendered dialog box
+ * consists of a header that contains the dialog title, a body with the message, and a footer that
+ * contains any {@link OO.ui.ActionWidget action widgets}. The MessageDialog class is the only type
+ * of {@link OO.ui.Dialog dialog} that is usually instantiated directly.
+ *
+ * There are two basic types of message dialogs, confirmation and alert:
+ *
+ * - **confirmation**: the dialog title describes what a progressive action will do and the message provides
+ * more details about the consequences.
+ * - **alert**: the dialog title describes which event occurred and the message provides more information
+ * about why the event occurred.
+ *
+ * The MessageDialog class specifies two actions: ‘accept’, the primary
+ * action (e.g., ‘ok’) and ‘reject,’ the safe action (e.g., ‘cancel’). Both will close the window,
+ * passing along the selected action.
+ *
+ * For more information and examples, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * @example
+ * // Example: Creating and opening a message dialog window.
+ * var messageDialog = new OO.ui.MessageDialog();
+ *
+ * // Create and append a window manager.
+ * var windowManager = new OO.ui.WindowManager();
+ * $( 'body' ).append( windowManager.$element );
+ * windowManager.addWindows( [ messageDialog ] );
+ * // Open the window.
+ * windowManager.openWindow( messageDialog, {
+ * title: 'Basic message dialog',
+ * message: 'This is the message'
+ * } );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Message_Dialogs
+ *
+ * @class
+ * @extends OO.ui.Dialog
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.MessageDialog = function OoUiMessageDialog( config ) {
+ // Parent constructor
+ OO.ui.MessageDialog.super.call( this, config );
+
+ // Properties
+ this.verticalActionLayout = null;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-messageDialog' );
+};
+
+/* Inheritance */
+
+OO.inheritClass( OO.ui.MessageDialog, OO.ui.Dialog );
+
+/* Static Properties */
+
+OO.ui.MessageDialog.static.name = 'message';
+
+OO.ui.MessageDialog.static.size = 'small';
+
+OO.ui.MessageDialog.static.verbose = false;
+
+/**
+ * Dialog title.
+ *
+ * The title of a confirmation dialog describes what a progressive action will do. The
+ * title of an alert dialog describes which event occurred.
+ *
+ * @static
+ * @inheritable
+ * @property {jQuery|string|Function|null}
+ */
+OO.ui.MessageDialog.static.title = null;
+
+/**
+ * The message displayed in the dialog body.
+ *
+ * A confirmation message describes the consequences of a progressive action. An alert
+ * message describes why an event occurred.
+ *
+ * @static
+ * @inheritable
+ * @property {jQuery|string|Function|null}
+ */
+OO.ui.MessageDialog.static.message = null;
+
+OO.ui.MessageDialog.static.actions = [
+ { action: 'accept', label: OO.ui.deferMsg( 'ooui-dialog-message-accept' ), flags: 'primary' },
+ { action: 'reject', label: OO.ui.deferMsg( 'ooui-dialog-message-reject' ), flags: 'safe' }
+];
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.setManager = function ( manager ) {
+ OO.ui.MessageDialog.super.prototype.setManager.call( this, manager );
+
+ // Events
+ this.manager.connect( this, {
+ resize: 'onResize'
+ } );
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.onActionResize = function ( action ) {
+ this.fitActions();
+ return OO.ui.MessageDialog.super.prototype.onActionResize.call( this, action );
+};
+
+/**
+ * Handle window resized events.
+ *
+ * @private
+ */
+OO.ui.MessageDialog.prototype.onResize = function () {
+ var dialog = this;
+ dialog.fitActions();
+ // Wait for CSS transition to finish and do it again :(
+ setTimeout( function () {
+ dialog.fitActions();
+ }, 300 );
+};
+
+/**
+ * Toggle action layout between vertical and horizontal.
+ *
+ *
+ * @private
+ * @param {boolean} [value] Layout actions vertically, omit to toggle
+ * @chainable
+ */
+OO.ui.MessageDialog.prototype.toggleVerticalActionLayout = function ( value ) {
+ value = value === undefined ? !this.verticalActionLayout : !!value;
+
+ if ( value !== this.verticalActionLayout ) {
+ this.verticalActionLayout = value;
+ this.$actions
+ .toggleClass( 'oo-ui-messageDialog-actions-vertical', value )
+ .toggleClass( 'oo-ui-messageDialog-actions-horizontal', !value );
+ }
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.getActionProcess = function ( action ) {
+ if ( action ) {
+ return new OO.ui.Process( function () {
+ this.close( { action: action } );
+ }, this );
+ }
+ return OO.ui.MessageDialog.super.prototype.getActionProcess.call( this, action );
+};
+
+/**
+ * @inheritdoc
+ *
+ * @param {Object} [data] Dialog opening data
+ * @param {jQuery|string|Function|null} [data.title] Description of the action being confirmed
+ * @param {jQuery|string|Function|null} [data.message] Description of the action's consequence
+ * @param {boolean} [data.verbose] Message is verbose and should be styled as a long message
+ * @param {Object[]} [data.actions] List of OO.ui.ActionOptionWidget configuration options for each
+ * action item
+ */
+OO.ui.MessageDialog.prototype.getSetupProcess = function ( data ) {
+ data = data || {};
+
+ // Parent method
+ return OO.ui.MessageDialog.super.prototype.getSetupProcess.call( this, data )
+ .next( function () {
+ this.title.setLabel(
+ data.title !== undefined ? data.title : this.constructor.static.title
+ );
+ this.message.setLabel(
+ data.message !== undefined ? data.message : this.constructor.static.message
+ );
+ this.message.$element.toggleClass(
+ 'oo-ui-messageDialog-message-verbose',
+ data.verbose !== undefined ? data.verbose : this.constructor.static.verbose
+ );
+ }, this );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.getBodyHeight = function () {
+ var bodyHeight, oldOverflow,
+ $scrollable = this.container.$element;
+
+ oldOverflow = $scrollable[ 0 ].style.overflow;
+ $scrollable[ 0 ].style.overflow = 'hidden';
+
+ OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
+
+ bodyHeight = this.text.$element.outerHeight( true );
+ $scrollable[ 0 ].style.overflow = oldOverflow;
+
+ return bodyHeight;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.setDimensions = function ( dim ) {
+ var $scrollable = this.container.$element;
+ OO.ui.MessageDialog.super.prototype.setDimensions.call( this, dim );
+
+ // Twiddle the overflow property, otherwise an unnecessary scrollbar will be produced.
+ // Need to do it after transition completes (250ms), add 50ms just in case.
+ setTimeout( function () {
+ var oldOverflow = $scrollable[ 0 ].style.overflow;
+ $scrollable[ 0 ].style.overflow = 'hidden';
+
+ OO.ui.Element.static.reconsiderScrollbars( $scrollable[ 0 ] );
+
+ $scrollable[ 0 ].style.overflow = oldOverflow;
+ }, 300 );
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.initialize = function () {
+ // Parent method
+ OO.ui.MessageDialog.super.prototype.initialize.call( this );
+
+ // Properties
+ this.$actions = $( '<div>' );
+ this.container = new OO.ui.PanelLayout( {
+ scrollable: true, classes: [ 'oo-ui-messageDialog-container' ]
+ } );
+ this.text = new OO.ui.PanelLayout( {
+ padded: true, expanded: false, classes: [ 'oo-ui-messageDialog-text' ]
+ } );
+ this.message = new OO.ui.LabelWidget( {
+ classes: [ 'oo-ui-messageDialog-message' ]
+ } );
+
+ // Initialization
+ this.title.$element.addClass( 'oo-ui-messageDialog-title' );
+ this.$content.addClass( 'oo-ui-messageDialog-content' );
+ this.container.$element.append( this.text.$element );
+ this.text.$element.append( this.title.$element, this.message.$element );
+ this.$body.append( this.container.$element );
+ this.$actions.addClass( 'oo-ui-messageDialog-actions' );
+ this.$foot.append( this.$actions );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MessageDialog.prototype.attachActions = function () {
+ var i, len, other, special, others;
+
+ // Parent method
+ OO.ui.MessageDialog.super.prototype.attachActions.call( this );
+
+ special = this.actions.getSpecial();
+ others = this.actions.getOthers();
+ if ( special.safe ) {
+ this.$actions.append( special.safe.$element );
+ special.safe.toggleFramed( false );
+ }
+ if ( others.length ) {
+ for ( i = 0, len = others.length; i < len; i++ ) {
+ other = others[ i ];
+ this.$actions.append( other.$element );
+ other.toggleFramed( false );
+ }
+ }
+ if ( special.primary ) {
+ this.$actions.append( special.primary.$element );
+ special.primary.toggleFramed( false );
+ }
+
+ if ( !this.isOpening() ) {
+ // If the dialog is currently opening, this will be called automatically soon.
+ // This also calls #fitActions.
+ this.updateSize();
+ }
+};
+
+/**
+ * Fit action actions into columns or rows.
+ *
+ * Columns will be used if all labels can fit without overflow, otherwise rows will be used.
+ *
+ * @private
+ */
+OO.ui.MessageDialog.prototype.fitActions = function () {
+ var i, len, action,
+ previous = this.verticalActionLayout,
+ actions = this.actions.get();
+
+ // Detect clipping
+ this.toggleVerticalActionLayout( false );
+ for ( i = 0, len = actions.length; i < len; i++ ) {
+ action = actions[ i ];
+ if ( action.$element.innerWidth() < action.$label.outerWidth( true ) ) {
+ this.toggleVerticalActionLayout( true );
+ break;
+ }
+ }
+
+ // Move the body out of the way of the foot
+ this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
+
+ if ( this.verticalActionLayout !== previous ) {
+ // We changed the layout, window height might need to be updated.
+ this.updateSize();
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/dialogs/ProcessDialog.js b/vendor/oojs/oojs-ui/src/dialogs/ProcessDialog.js
new file mode 100644
index 00000000..d8f7c137
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/dialogs/ProcessDialog.js
@@ -0,0 +1,296 @@
+/**
+ * ProcessDialog windows encapsulate a {@link OO.ui.Process process} and all of the code necessary
+ * to complete it. If the process terminates with an error, a customizable {@link OO.ui.Error error
+ * interface} alerts users to the trouble, permitting the user to dismiss the error and try again when
+ * relevant. The ProcessDialog class is always extended and customized with the actions and content
+ * required for each process.
+ *
+ * The process dialog box consists of a header that visually represents the ‘working’ state of long
+ * processes with an animation. The header contains the dialog title as well as
+ * two {@link OO.ui.ActionWidget action widgets}: a ‘safe’ action on the left (e.g., ‘Cancel’) and
+ * a ‘primary’ action on the right (e.g., ‘Done’).
+ *
+ * Like other windows, the process dialog is managed by a {@link OO.ui.WindowManager window manager}.
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information and examples.
+ *
+ * @example
+ * // Example: Creating and opening a process dialog window.
+ * function MyProcessDialog( config ) {
+ * MyProcessDialog.super.call( this, config );
+ * }
+ * OO.inheritClass( MyProcessDialog, OO.ui.ProcessDialog );
+ *
+ * MyProcessDialog.static.title = 'Process dialog';
+ * MyProcessDialog.static.actions = [
+ * { action: 'save', label: 'Done', flags: 'primary' },
+ * { label: 'Cancel', flags: 'safe' }
+ * ];
+ *
+ * MyProcessDialog.prototype.initialize = function () {
+ * MyProcessDialog.super.prototype.initialize.apply( this, arguments );
+ * this.content = new OO.ui.PanelLayout( { padded: true, expanded: false } );
+ * this.content.$element.append( '<p>This is a process dialog window. The header contains the title and two buttons: \'Cancel\' (a safe action) on the left and \'Done\' (a primary action) on the right.</p>' );
+ * this.$body.append( this.content.$element );
+ * };
+ * MyProcessDialog.prototype.getActionProcess = function ( action ) {
+ * var dialog = this;
+ * if ( action ) {
+ * return new OO.ui.Process( function () {
+ * dialog.close( { action: action } );
+ * } );
+ * }
+ * return MyProcessDialog.super.prototype.getActionProcess.call( this, action );
+ * };
+ *
+ * var windowManager = new OO.ui.WindowManager();
+ * $( 'body' ).append( windowManager.$element );
+ *
+ * var dialog = new MyProcessDialog();
+ * windowManager.addWindows( [ dialog ] );
+ * windowManager.openWindow( dialog );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Dialog
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.ProcessDialog = function OoUiProcessDialog( config ) {
+ // Parent constructor
+ OO.ui.ProcessDialog.super.call( this, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-processDialog' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ProcessDialog, OO.ui.Dialog );
+
+/* Methods */
+
+/**
+ * Handle dismiss button click events.
+ *
+ * Hides errors.
+ *
+ * @private
+ */
+OO.ui.ProcessDialog.prototype.onDismissErrorButtonClick = function () {
+ this.hideErrors();
+};
+
+/**
+ * Handle retry button click events.
+ *
+ * Hides errors and then tries again.
+ *
+ * @private
+ */
+OO.ui.ProcessDialog.prototype.onRetryButtonClick = function () {
+ this.hideErrors();
+ this.executeAction( this.currentAction );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ProcessDialog.prototype.onActionResize = function ( action ) {
+ if ( this.actions.isSpecial( action ) ) {
+ this.fitLabel();
+ }
+ return OO.ui.ProcessDialog.super.prototype.onActionResize.call( this, action );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ProcessDialog.prototype.initialize = function () {
+ // Parent method
+ OO.ui.ProcessDialog.super.prototype.initialize.call( this );
+
+ // Properties
+ this.$navigation = $( '<div>' );
+ this.$location = $( '<div>' );
+ this.$safeActions = $( '<div>' );
+ this.$primaryActions = $( '<div>' );
+ this.$otherActions = $( '<div>' );
+ this.dismissButton = new OO.ui.ButtonWidget( {
+ label: OO.ui.msg( 'ooui-dialog-process-dismiss' )
+ } );
+ this.retryButton = new OO.ui.ButtonWidget();
+ this.$errors = $( '<div>' );
+ this.$errorsTitle = $( '<div>' );
+
+ // Events
+ this.dismissButton.connect( this, { click: 'onDismissErrorButtonClick' } );
+ this.retryButton.connect( this, { click: 'onRetryButtonClick' } );
+
+ // Initialization
+ this.title.$element.addClass( 'oo-ui-processDialog-title' );
+ this.$location
+ .append( this.title.$element )
+ .addClass( 'oo-ui-processDialog-location' );
+ this.$safeActions.addClass( 'oo-ui-processDialog-actions-safe' );
+ this.$primaryActions.addClass( 'oo-ui-processDialog-actions-primary' );
+ this.$otherActions.addClass( 'oo-ui-processDialog-actions-other' );
+ this.$errorsTitle
+ .addClass( 'oo-ui-processDialog-errors-title' )
+ .text( OO.ui.msg( 'ooui-dialog-process-error' ) );
+ this.$errors
+ .addClass( 'oo-ui-processDialog-errors oo-ui-element-hidden' )
+ .append( this.$errorsTitle, this.dismissButton.$element, this.retryButton.$element );
+ this.$content
+ .addClass( 'oo-ui-processDialog-content' )
+ .append( this.$errors );
+ this.$navigation
+ .addClass( 'oo-ui-processDialog-navigation' )
+ .append( this.$safeActions, this.$location, this.$primaryActions );
+ this.$head.append( this.$navigation );
+ this.$foot.append( this.$otherActions );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ProcessDialog.prototype.getActionWidgets = function ( actions ) {
+ var i, len, widgets = [];
+ for ( i = 0, len = actions.length; i < len; i++ ) {
+ widgets.push(
+ new OO.ui.ActionWidget( $.extend( { framed: true }, actions[ i ] ) )
+ );
+ }
+ return widgets;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ProcessDialog.prototype.attachActions = function () {
+ var i, len, other, special, others;
+
+ // Parent method
+ OO.ui.ProcessDialog.super.prototype.attachActions.call( this );
+
+ special = this.actions.getSpecial();
+ others = this.actions.getOthers();
+ if ( special.primary ) {
+ this.$primaryActions.append( special.primary.$element );
+ }
+ for ( i = 0, len = others.length; i < len; i++ ) {
+ other = others[ i ];
+ this.$otherActions.append( other.$element );
+ }
+ if ( special.safe ) {
+ this.$safeActions.append( special.safe.$element );
+ }
+
+ this.fitLabel();
+ this.$body.css( 'bottom', this.$foot.outerHeight( true ) );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ProcessDialog.prototype.executeAction = function ( action ) {
+ var process = this;
+ return OO.ui.ProcessDialog.super.prototype.executeAction.call( this, action )
+ .fail( function ( errors ) {
+ process.showErrors( errors || [] );
+ } );
+};
+
+/**
+ * Fit label between actions.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.ProcessDialog.prototype.fitLabel = function () {
+ var width = Math.max(
+ this.$safeActions.is( ':visible' ) ? this.$safeActions.width() : 0,
+ this.$primaryActions.is( ':visible' ) ? this.$primaryActions.width() : 0
+ );
+ this.$location.css( { paddingLeft: width, paddingRight: width } );
+
+ return this;
+};
+
+/**
+ * Handle errors that occurred during accept or reject processes.
+ *
+ * @private
+ * @param {OO.ui.Error[]|OO.ui.Error} errors Errors to be handled
+ */
+OO.ui.ProcessDialog.prototype.showErrors = function ( errors ) {
+ var i, len, $item, actions,
+ items = [],
+ abilities = {},
+ recoverable = true,
+ warning = false;
+
+ if ( errors instanceof OO.ui.Error ) {
+ errors = [ errors ];
+ }
+
+ for ( i = 0, len = errors.length; i < len; i++ ) {
+ if ( !errors[ i ].isRecoverable() ) {
+ recoverable = false;
+ }
+ if ( errors[ i ].isWarning() ) {
+ warning = true;
+ }
+ $item = $( '<div>' )
+ .addClass( 'oo-ui-processDialog-error' )
+ .append( errors[ i ].getMessage() );
+ items.push( $item[ 0 ] );
+ }
+ this.$errorItems = $( items );
+ if ( recoverable ) {
+ abilities[this.currentAction] = true;
+ // Copy the flags from the first matching action
+ actions = this.actions.get( { actions: this.currentAction } );
+ if ( actions.length ) {
+ this.retryButton.clearFlags().setFlags( actions[0].getFlags() );
+ }
+ } else {
+ abilities[this.currentAction] = false;
+ this.actions.setAbilities( abilities );
+ }
+ if ( warning ) {
+ this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-continue' ) );
+ } else {
+ this.retryButton.setLabel( OO.ui.msg( 'ooui-dialog-process-retry' ) );
+ }
+ this.retryButton.toggle( recoverable );
+ this.$errorsTitle.after( this.$errorItems );
+ this.$errors.removeClass( 'oo-ui-element-hidden' ).scrollTop( 0 );
+};
+
+/**
+ * Hide errors.
+ *
+ * @private
+ */
+OO.ui.ProcessDialog.prototype.hideErrors = function () {
+ this.$errors.addClass( 'oo-ui-element-hidden' );
+ if ( this.$errorItems ) {
+ this.$errorItems.remove();
+ this.$errorItems = null;
+ }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ProcessDialog.prototype.getTeardownProcess = function ( data ) {
+ // Parent method
+ return OO.ui.ProcessDialog.super.prototype.getTeardownProcess.call( this, data )
+ .first( function () {
+ // Make sure to hide errors
+ this.hideErrors();
+ }, this );
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/ButtonElement.js b/vendor/oojs/oojs-ui/src/elements/ButtonElement.js
new file mode 100644
index 00000000..6d338ed7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/ButtonElement.js
@@ -0,0 +1,263 @@
+/**
+ * ButtonElement is often mixed into other classes to generate a button, which is a clickable
+ * interface element that can be configured with access keys for accessibility.
+ * See the [OOjs UI documentation on MediaWiki] [1] for examples.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Buttons
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$button] The button element created by the class.
+ * If this configuration is omitted, the button element will use a generated `<a>`.
+ * @cfg {boolean} [framed=true] Render the button with a frame
+ * @cfg {string} [accessKey] Button's access key
+ */
+OO.ui.ButtonElement = function OoUiButtonElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$button = null;
+ this.framed = null;
+ this.accessKey = null;
+ this.active = false;
+ this.onMouseUpHandler = this.onMouseUp.bind( this );
+ this.onMouseDownHandler = this.onMouseDown.bind( this );
+ this.onKeyDownHandler = this.onKeyDown.bind( this );
+ this.onKeyUpHandler = this.onKeyUp.bind( this );
+ this.onClickHandler = this.onClick.bind( this );
+ this.onKeyPressHandler = this.onKeyPress.bind( this );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-buttonElement' );
+ this.toggleFramed( config.framed === undefined || config.framed );
+ this.setAccessKey( config.accessKey );
+ this.setButtonElement( config.$button || $( '<a>' ) );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.ButtonElement );
+
+/* Static Properties */
+
+/**
+ * Cancel mouse down events.
+ *
+ * This property is usually set to `true` to prevent the focus from changing when the button is clicked.
+ * Classes such as {@link OO.ui.DraggableElement DraggableElement} and {@link OO.ui.ButtonOptionWidget ButtonOptionWidget}
+ * use a value of `false` so that dragging behavior is possible and mousedown events can be handled by a
+ * parent widget.
+ *
+ * @static
+ * @inheritable
+ * @property {boolean}
+ */
+OO.ui.ButtonElement.static.cancelButtonMouseDownEvents = true;
+
+/* Events */
+
+/**
+ * A 'click' event is emitted when the button element is clicked.
+ *
+ * @event click
+ */
+
+/* Methods */
+
+/**
+ * Set the button element.
+ *
+ * This method is used to retarget a button mixin so that its functionality applies to
+ * the specified button element instead of the one created by the class. If a button element
+ * is already set, the method will remove the mixin’s effect on that element.
+ *
+ * @param {jQuery} $button Element to use as button
+ */
+OO.ui.ButtonElement.prototype.setButtonElement = function ( $button ) {
+ if ( this.$button ) {
+ this.$button
+ .removeClass( 'oo-ui-buttonElement-button' )
+ .removeAttr( 'role accesskey' )
+ .off( {
+ mousedown: this.onMouseDownHandler,
+ keydown: this.onKeyDownHandler,
+ click: this.onClickHandler,
+ keypress: this.onKeyPressHandler
+ } );
+ }
+
+ this.$button = $button
+ .addClass( 'oo-ui-buttonElement-button' )
+ .attr( { role: 'button', accesskey: this.accessKey } )
+ .on( {
+ mousedown: this.onMouseDownHandler,
+ keydown: this.onKeyDownHandler,
+ click: this.onClickHandler,
+ keypress: this.onKeyPressHandler
+ } );
+};
+
+/**
+ * Handles mouse down events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Mouse down event
+ */
+OO.ui.ButtonElement.prototype.onMouseDown = function ( e ) {
+ if ( this.isDisabled() || e.which !== 1 ) {
+ return;
+ }
+ this.$element.addClass( 'oo-ui-buttonElement-pressed' );
+ // Run the mouseup handler no matter where the mouse is when the button is let go, so we can
+ // reliably remove the pressed class
+ this.getElementDocument().addEventListener( 'mouseup', this.onMouseUpHandler, true );
+ // Prevent change of focus unless specifically configured otherwise
+ if ( this.constructor.static.cancelButtonMouseDownEvents ) {
+ return false;
+ }
+};
+
+/**
+ * Handles mouse up events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Mouse up event
+ */
+OO.ui.ButtonElement.prototype.onMouseUp = function ( e ) {
+ if ( this.isDisabled() || e.which !== 1 ) {
+ return;
+ }
+ this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
+ // Stop listening for mouseup, since we only needed this once
+ this.getElementDocument().removeEventListener( 'mouseup', this.onMouseUpHandler, true );
+};
+
+/**
+ * Handles mouse click events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Mouse click event
+ * @fires click
+ */
+OO.ui.ButtonElement.prototype.onClick = function ( e ) {
+ if ( !this.isDisabled() && e.which === 1 ) {
+ if ( this.emit( 'click' ) ) {
+ return false;
+ }
+ }
+};
+
+/**
+ * Handles key down events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.ButtonElement.prototype.onKeyDown = function ( e ) {
+ if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
+ return;
+ }
+ this.$element.addClass( 'oo-ui-buttonElement-pressed' );
+ // Run the keyup handler no matter where the key is when the button is let go, so we can
+ // reliably remove the pressed class
+ this.getElementDocument().addEventListener( 'keyup', this.onKeyUpHandler, true );
+};
+
+/**
+ * Handles key up events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Key up event
+ */
+OO.ui.ButtonElement.prototype.onKeyUp = function ( e ) {
+ if ( this.isDisabled() || ( e.which !== OO.ui.Keys.SPACE && e.which !== OO.ui.Keys.ENTER ) ) {
+ return;
+ }
+ this.$element.removeClass( 'oo-ui-buttonElement-pressed' );
+ // Stop listening for keyup, since we only needed this once
+ this.getElementDocument().removeEventListener( 'keyup', this.onKeyUpHandler, true );
+};
+
+/**
+ * Handles key press events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Key press event
+ * @fires click
+ */
+OO.ui.ButtonElement.prototype.onKeyPress = function ( e ) {
+ if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
+ if ( this.emit( 'click' ) ) {
+ return false;
+ }
+ }
+};
+
+/**
+ * Check if button has a frame.
+ *
+ * @return {boolean} Button is framed
+ */
+OO.ui.ButtonElement.prototype.isFramed = function () {
+ return this.framed;
+};
+
+/**
+ * Render the button with or without a frame. Omit the `framed` parameter to toggle the button frame on and off.
+ *
+ * @param {boolean} [framed] Make button framed, omit to toggle
+ * @chainable
+ */
+OO.ui.ButtonElement.prototype.toggleFramed = function ( framed ) {
+ framed = framed === undefined ? !this.framed : !!framed;
+ if ( framed !== this.framed ) {
+ this.framed = framed;
+ this.$element
+ .toggleClass( 'oo-ui-buttonElement-frameless', !framed )
+ .toggleClass( 'oo-ui-buttonElement-framed', framed );
+ this.updateThemeClasses();
+ }
+
+ return this;
+};
+
+/**
+ * Set the button's access key.
+ *
+ * @param {string} accessKey Button's access key, use empty string to remove
+ * @chainable
+ */
+OO.ui.ButtonElement.prototype.setAccessKey = function ( accessKey ) {
+ accessKey = typeof accessKey === 'string' && accessKey.length ? accessKey : null;
+
+ if ( this.accessKey !== accessKey ) {
+ if ( this.$button ) {
+ if ( accessKey !== null ) {
+ this.$button.attr( 'accesskey', accessKey );
+ } else {
+ this.$button.removeAttr( 'accesskey' );
+ }
+ }
+ this.accessKey = accessKey;
+ }
+
+ return this;
+};
+
+/**
+ * Set the button to its 'active' state.
+ *
+ * The active state occurs when a {@link OO.ui.ButtonOptionWidget ButtonOptionWidget} or
+ * a {@link OO.ui.ToggleButtonWidget ToggleButtonWidget} is pressed. This method does nothing
+ * for other button types.
+ *
+ * @param {boolean} [value] Make button active
+ * @chainable
+ */
+OO.ui.ButtonElement.prototype.setActive = function ( value ) {
+ this.$element.toggleClass( 'oo-ui-buttonElement-active', !!value );
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/ClippableElement.js b/vendor/oojs/oojs-ui/src/elements/ClippableElement.js
new file mode 100644
index 00000000..33b0b234
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/ClippableElement.js
@@ -0,0 +1,205 @@
+/**
+ * Element that can be automatically clipped to visible boundaries.
+ *
+ * Whenever the element's natural height changes, you have to call
+ * #clip to make sure it's still clipping correctly.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$clippable] Nodes to clip, assigned to #$clippable, omit to use #$element
+ */
+OO.ui.ClippableElement = function OoUiClippableElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$clippable = null;
+ this.clipping = false;
+ this.clippedHorizontally = false;
+ this.clippedVertically = false;
+ this.$clippableContainer = null;
+ this.$clippableScroller = null;
+ this.$clippableWindow = null;
+ this.idealWidth = null;
+ this.idealHeight = null;
+ this.onClippableContainerScrollHandler = this.clip.bind( this );
+ this.onClippableWindowResizeHandler = this.clip.bind( this );
+
+ // Initialization
+ this.setClippableElement( config.$clippable || this.$element );
+};
+
+/* Methods */
+
+/**
+ * Set clippable element.
+ *
+ * If an element is already set, it will be cleaned up before setting up the new element.
+ *
+ * @param {jQuery} $clippable Element to make clippable
+ */
+OO.ui.ClippableElement.prototype.setClippableElement = function ( $clippable ) {
+ if ( this.$clippable ) {
+ this.$clippable.removeClass( 'oo-ui-clippableElement-clippable' );
+ this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } );
+ OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
+ }
+
+ this.$clippable = $clippable.addClass( 'oo-ui-clippableElement-clippable' );
+ this.clip();
+};
+
+/**
+ * Toggle clipping.
+ *
+ * Do not turn clipping on until after the element is attached to the DOM and visible.
+ *
+ * @param {boolean} [clipping] Enable clipping, omit to toggle
+ * @chainable
+ */
+OO.ui.ClippableElement.prototype.toggleClipping = function ( clipping ) {
+ clipping = clipping === undefined ? !this.clipping : !!clipping;
+
+ if ( this.clipping !== clipping ) {
+ this.clipping = clipping;
+ if ( clipping ) {
+ this.$clippableContainer = $( this.getClosestScrollableElementContainer() );
+ // If the clippable container is the root, we have to listen to scroll events and check
+ // jQuery.scrollTop on the window because of browser inconsistencies
+ this.$clippableScroller = this.$clippableContainer.is( 'html, body' ) ?
+ $( OO.ui.Element.static.getWindow( this.$clippableContainer ) ) :
+ this.$clippableContainer;
+ this.$clippableScroller.on( 'scroll', this.onClippableContainerScrollHandler );
+ this.$clippableWindow = $( this.getElementWindow() )
+ .on( 'resize', this.onClippableWindowResizeHandler );
+ // Initial clip after visible
+ this.clip();
+ } else {
+ this.$clippable.css( { width: '', height: '', overflowX: '', overflowY: '' } );
+ OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
+
+ this.$clippableContainer = null;
+ this.$clippableScroller.off( 'scroll', this.onClippableContainerScrollHandler );
+ this.$clippableScroller = null;
+ this.$clippableWindow.off( 'resize', this.onClippableWindowResizeHandler );
+ this.$clippableWindow = null;
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Check if the element will be clipped to fit the visible area of the nearest scrollable container.
+ *
+ * @return {boolean} Element will be clipped to the visible area
+ */
+OO.ui.ClippableElement.prototype.isClipping = function () {
+ return this.clipping;
+};
+
+/**
+ * Check if the bottom or right of the element is being clipped by the nearest scrollable container.
+ *
+ * @return {boolean} Part of the element is being clipped
+ */
+OO.ui.ClippableElement.prototype.isClipped = function () {
+ return this.clippedHorizontally || this.clippedVertically;
+};
+
+/**
+ * Check if the right of the element is being clipped by the nearest scrollable container.
+ *
+ * @return {boolean} Part of the element is being clipped
+ */
+OO.ui.ClippableElement.prototype.isClippedHorizontally = function () {
+ return this.clippedHorizontally;
+};
+
+/**
+ * Check if the bottom of the element is being clipped by the nearest scrollable container.
+ *
+ * @return {boolean} Part of the element is being clipped
+ */
+OO.ui.ClippableElement.prototype.isClippedVertically = function () {
+ return this.clippedVertically;
+};
+
+/**
+ * Set the ideal size. These are the dimensions the element will have when it's not being clipped.
+ *
+ * @param {number|string} [width] Width as a number of pixels or CSS string with unit suffix
+ * @param {number|string} [height] Height as a number of pixels or CSS string with unit suffix
+ */
+OO.ui.ClippableElement.prototype.setIdealSize = function ( width, height ) {
+ this.idealWidth = width;
+ this.idealHeight = height;
+
+ if ( !this.clipping ) {
+ // Update dimensions
+ this.$clippable.css( { width: width, height: height } );
+ }
+ // While clipping, idealWidth and idealHeight are not considered
+};
+
+/**
+ * Clip element to visible boundaries and allow scrolling when needed. Call this method when
+ * the element's natural height changes.
+ *
+ * Element will be clipped the bottom or right of the element is within 10px of the edge of, or
+ * overlapped by, the visible area of the nearest scrollable container.
+ *
+ * @chainable
+ */
+OO.ui.ClippableElement.prototype.clip = function () {
+ if ( !this.clipping ) {
+ // this.$clippableContainer and this.$clippableWindow are null, so the below will fail
+ return this;
+ }
+
+ var buffer = 7, // Chosen by fair dice roll
+ cOffset = this.$clippable.offset(),
+ $container = this.$clippableContainer.is( 'html, body' ) ?
+ this.$clippableWindow : this.$clippableContainer,
+ ccOffset = $container.offset() || { top: 0, left: 0 },
+ ccHeight = $container.innerHeight() - buffer,
+ ccWidth = $container.innerWidth() - buffer,
+ cHeight = this.$clippable.outerHeight() + buffer,
+ cWidth = this.$clippable.outerWidth() + buffer,
+ scrollTop = this.$clippableScroller.scrollTop(),
+ scrollLeft = this.$clippableScroller.scrollLeft(),
+ desiredWidth = cOffset.left < 0 ?
+ cWidth + cOffset.left :
+ ( ccOffset.left + scrollLeft + ccWidth ) - cOffset.left,
+ desiredHeight = cOffset.top < 0 ?
+ cHeight + cOffset.top :
+ ( ccOffset.top + scrollTop + ccHeight ) - cOffset.top,
+ naturalWidth = this.$clippable.prop( 'scrollWidth' ),
+ naturalHeight = this.$clippable.prop( 'scrollHeight' ),
+ clipWidth = desiredWidth < naturalWidth,
+ clipHeight = desiredHeight < naturalHeight;
+
+ if ( clipWidth ) {
+ this.$clippable.css( { overflowX: 'scroll', width: desiredWidth } );
+ } else {
+ this.$clippable.css( { width: this.idealWidth || '', overflowX: '' } );
+ }
+ if ( clipHeight ) {
+ this.$clippable.css( { overflowY: 'scroll', height: desiredHeight } );
+ } else {
+ this.$clippable.css( { height: this.idealHeight || '', overflowY: '' } );
+ }
+
+ // If we stopped clipping in at least one of the dimensions
+ if ( !clipWidth || !clipHeight ) {
+ OO.ui.Element.static.reconsiderScrollbars( this.$clippable[ 0 ] );
+ }
+
+ this.clippedHorizontally = clipWidth;
+ this.clippedVertically = clipHeight;
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/DraggableElement.js b/vendor/oojs/oojs-ui/src/elements/DraggableElement.js
new file mode 100644
index 00000000..9ae4d5bb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/DraggableElement.js
@@ -0,0 +1,142 @@
+/**
+ * DraggableElement is a mixin class used to create elements that can be clicked
+ * and dragged by a mouse to a new position within a group. This class must be used
+ * in conjunction with OO.ui.DraggableGroupElement, which provides a container for
+ * the draggable elements.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ */
+OO.ui.DraggableElement = function OoUiDraggableElement() {
+ // Properties
+ this.index = null;
+
+ // Initialize and events
+ this.$element
+ .attr( 'draggable', true )
+ .addClass( 'oo-ui-draggableElement' )
+ .on( {
+ dragstart: this.onDragStart.bind( this ),
+ dragover: this.onDragOver.bind( this ),
+ dragend: this.onDragEnd.bind( this ),
+ drop: this.onDrop.bind( this )
+ } );
+};
+
+OO.initClass( OO.ui.DraggableElement );
+
+/* Events */
+
+/**
+ * @event dragstart
+ *
+ * A dragstart event is emitted when the user clicks and begins dragging an item.
+ * @param {OO.ui.DraggableElement} item The item the user has clicked and is dragging with the mouse.
+ */
+
+/**
+ * @event dragend
+ * A dragend event is emitted when the user drags an item and releases the mouse,
+ * thus terminating the drag operation.
+ */
+
+/**
+ * @event drop
+ * A drop event is emitted when the user drags an item and then releases the mouse button
+ * over a valid target.
+ */
+
+/* Static Properties */
+
+/**
+ * @inheritdoc OO.ui.ButtonElement
+ */
+OO.ui.DraggableElement.static.cancelButtonMouseDownEvents = false;
+
+/* Methods */
+
+/**
+ * Respond to dragstart event.
+ *
+ * @private
+ * @param {jQuery.Event} event jQuery event
+ * @fires dragstart
+ */
+OO.ui.DraggableElement.prototype.onDragStart = function ( e ) {
+ var dataTransfer = e.originalEvent.dataTransfer;
+ // Define drop effect
+ dataTransfer.dropEffect = 'none';
+ dataTransfer.effectAllowed = 'move';
+ // We must set up a dataTransfer data property or Firefox seems to
+ // ignore the fact the element is draggable.
+ try {
+ dataTransfer.setData( 'application-x/OOjs-UI-draggable', this.getIndex() );
+ } catch ( err ) {
+ // The above is only for firefox. No need to set a catch clause
+ // if it fails, move on.
+ }
+ // Add dragging class
+ this.$element.addClass( 'oo-ui-draggableElement-dragging' );
+ // Emit event
+ this.emit( 'dragstart', this );
+ return true;
+};
+
+/**
+ * Respond to dragend event.
+ *
+ * @private
+ * @fires dragend
+ */
+OO.ui.DraggableElement.prototype.onDragEnd = function () {
+ this.$element.removeClass( 'oo-ui-draggableElement-dragging' );
+ this.emit( 'dragend' );
+};
+
+/**
+ * Handle drop event.
+ *
+ * @private
+ * @param {jQuery.Event} event jQuery event
+ * @fires drop
+ */
+OO.ui.DraggableElement.prototype.onDrop = function ( e ) {
+ e.preventDefault();
+ this.emit( 'drop', e );
+};
+
+/**
+ * In order for drag/drop to work, the dragover event must
+ * return false and stop propogation.
+ *
+ * @private
+ */
+OO.ui.DraggableElement.prototype.onDragOver = function ( e ) {
+ e.preventDefault();
+};
+
+/**
+ * Set item index.
+ * Store it in the DOM so we can access from the widget drag event
+ *
+ * @private
+ * @param {number} Item index
+ */
+OO.ui.DraggableElement.prototype.setIndex = function ( index ) {
+ if ( this.index !== index ) {
+ this.index = index;
+ this.$element.data( 'index', index );
+ }
+};
+
+/**
+ * Get item index
+ *
+ * @private
+ * @return {number} Item index
+ */
+OO.ui.DraggableElement.prototype.getIndex = function () {
+ return this.index;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/DraggableGroupElement.js b/vendor/oojs/oojs-ui/src/elements/DraggableGroupElement.js
new file mode 100644
index 00000000..134e2953
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/DraggableGroupElement.js
@@ -0,0 +1,261 @@
+/**
+ * DraggableGroupElement is a mixin class used to create a group element to
+ * contain draggable elements, which are items that can be clicked and dragged by a mouse.
+ * The class is used with OO.ui.DraggableElement.
+ *
+ * @abstract
+ * @class
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [orientation] Item orientation: 'horizontal' or 'vertical'. The orientation
+ * should match the layout of the items. Items displayed in a single row
+ * or in several rows should use horizontal orientation. The vertical orientation should only be
+ * used when the items are displayed in a single column. Defaults to 'vertical'
+ */
+OO.ui.DraggableGroupElement = function OoUiDraggableGroupElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.GroupElement.call( this, config );
+
+ // Properties
+ this.orientation = config.orientation || 'vertical';
+ this.dragItem = null;
+ this.itemDragOver = null;
+ this.itemKeys = {};
+ this.sideInsertion = '';
+
+ // Events
+ this.aggregate( {
+ dragstart: 'itemDragStart',
+ dragend: 'itemDragEnd',
+ drop: 'itemDrop'
+ } );
+ this.connect( this, {
+ itemDragStart: 'onItemDragStart',
+ itemDrop: 'onItemDrop',
+ itemDragEnd: 'onItemDragEnd'
+ } );
+ this.$element.on( {
+ dragover: $.proxy( this.onDragOver, this ),
+ dragleave: $.proxy( this.onDragLeave, this )
+ } );
+
+ // Initialize
+ if ( Array.isArray( config.items ) ) {
+ this.addItems( config.items );
+ }
+ this.$placeholder = $( '<div>' )
+ .addClass( 'oo-ui-draggableGroupElement-placeholder' );
+ this.$element
+ .addClass( 'oo-ui-draggableGroupElement' )
+ .append( this.$status )
+ .toggleClass( 'oo-ui-draggableGroupElement-horizontal', this.orientation === 'horizontal' )
+ .prepend( this.$placeholder );
+};
+
+/* Setup */
+OO.mixinClass( OO.ui.DraggableGroupElement, OO.ui.GroupElement );
+
+/* Events */
+
+/**
+ * A 'reorder' event is emitted when the order of items in the group changes.
+ *
+ * @event reorder
+ * @param {OO.ui.DraggableElement} item Reordered item
+ * @param {number} [newIndex] New index for the item
+ */
+
+/* Methods */
+
+/**
+ * Respond to item drag start event
+ *
+ * @private
+ * @param {OO.ui.DraggableElement} item Dragged item
+ */
+OO.ui.DraggableGroupElement.prototype.onItemDragStart = function ( item ) {
+ var i, len;
+
+ // Map the index of each object
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ this.items[ i ].setIndex( i );
+ }
+
+ if ( this.orientation === 'horizontal' ) {
+ // Set the height of the indicator
+ this.$placeholder.css( {
+ height: item.$element.outerHeight(),
+ width: 2
+ } );
+ } else {
+ // Set the width of the indicator
+ this.$placeholder.css( {
+ height: 2,
+ width: item.$element.outerWidth()
+ } );
+ }
+ this.setDragItem( item );
+};
+
+/**
+ * Respond to item drag end event
+ *
+ * @private
+ */
+OO.ui.DraggableGroupElement.prototype.onItemDragEnd = function () {
+ this.unsetDragItem();
+ return false;
+};
+
+/**
+ * Handle drop event and switch the order of the items accordingly
+ *
+ * @private
+ * @param {OO.ui.DraggableElement} item Dropped item
+ * @fires reorder
+ */
+OO.ui.DraggableGroupElement.prototype.onItemDrop = function ( item ) {
+ var toIndex = item.getIndex();
+ // Check if the dropped item is from the current group
+ // TODO: Figure out a way to configure a list of legally droppable
+ // elements even if they are not yet in the list
+ if ( this.getDragItem() ) {
+ // If the insertion point is 'after', the insertion index
+ // is shifted to the right (or to the left in RTL, hence 'after')
+ if ( this.sideInsertion === 'after' ) {
+ toIndex++;
+ }
+ // Emit change event
+ this.emit( 'reorder', this.getDragItem(), toIndex );
+ }
+ this.unsetDragItem();
+ // Return false to prevent propogation
+ return false;
+};
+
+/**
+ * Handle dragleave event.
+ *
+ * @private
+ */
+OO.ui.DraggableGroupElement.prototype.onDragLeave = function () {
+ // This means the item was dragged outside the widget
+ this.$placeholder
+ .css( 'left', 0 )
+ .addClass( 'oo-ui-element-hidden' );
+};
+
+/**
+ * Respond to dragover event
+ *
+ * @private
+ * @param {jQuery.Event} event Event details
+ */
+OO.ui.DraggableGroupElement.prototype.onDragOver = function ( e ) {
+ var dragOverObj, $optionWidget, itemOffset, itemMidpoint, itemBoundingRect,
+ itemSize, cssOutput, dragPosition, itemIndex, itemPosition,
+ clientX = e.originalEvent.clientX,
+ clientY = e.originalEvent.clientY;
+
+ // Get the OptionWidget item we are dragging over
+ dragOverObj = this.getElementDocument().elementFromPoint( clientX, clientY );
+ $optionWidget = $( dragOverObj ).closest( '.oo-ui-draggableElement' );
+ if ( $optionWidget[ 0 ] ) {
+ itemOffset = $optionWidget.offset();
+ itemBoundingRect = $optionWidget[ 0 ].getBoundingClientRect();
+ itemPosition = $optionWidget.position();
+ itemIndex = $optionWidget.data( 'index' );
+ }
+
+ if (
+ itemOffset &&
+ this.isDragging() &&
+ itemIndex !== this.getDragItem().getIndex()
+ ) {
+ if ( this.orientation === 'horizontal' ) {
+ // Calculate where the mouse is relative to the item width
+ itemSize = itemBoundingRect.width;
+ itemMidpoint = itemBoundingRect.left + itemSize / 2;
+ dragPosition = clientX;
+ // Which side of the item we hover over will dictate
+ // where the placeholder will appear, on the left or
+ // on the right
+ cssOutput = {
+ left: dragPosition < itemMidpoint ? itemPosition.left : itemPosition.left + itemSize,
+ top: itemPosition.top
+ };
+ } else {
+ // Calculate where the mouse is relative to the item height
+ itemSize = itemBoundingRect.height;
+ itemMidpoint = itemBoundingRect.top + itemSize / 2;
+ dragPosition = clientY;
+ // Which side of the item we hover over will dictate
+ // where the placeholder will appear, on the top or
+ // on the bottom
+ cssOutput = {
+ top: dragPosition < itemMidpoint ? itemPosition.top : itemPosition.top + itemSize,
+ left: itemPosition.left
+ };
+ }
+ // Store whether we are before or after an item to rearrange
+ // For horizontal layout, we need to account for RTL, as this is flipped
+ if ( this.orientation === 'horizontal' && this.$element.css( 'direction' ) === 'rtl' ) {
+ this.sideInsertion = dragPosition < itemMidpoint ? 'after' : 'before';
+ } else {
+ this.sideInsertion = dragPosition < itemMidpoint ? 'before' : 'after';
+ }
+ // Add drop indicator between objects
+ this.$placeholder
+ .css( cssOutput )
+ .removeClass( 'oo-ui-element-hidden' );
+ } else {
+ // This means the item was dragged outside the widget
+ this.$placeholder
+ .css( 'left', 0 )
+ .addClass( 'oo-ui-element-hidden' );
+ }
+ // Prevent default
+ e.preventDefault();
+};
+
+/**
+ * Set a dragged item
+ *
+ * @param {OO.ui.DraggableElement} item Dragged item
+ */
+OO.ui.DraggableGroupElement.prototype.setDragItem = function ( item ) {
+ this.dragItem = item;
+};
+
+/**
+ * Unset the current dragged item
+ */
+OO.ui.DraggableGroupElement.prototype.unsetDragItem = function () {
+ this.dragItem = null;
+ this.itemDragOver = null;
+ this.$placeholder.addClass( 'oo-ui-element-hidden' );
+ this.sideInsertion = '';
+};
+
+/**
+ * Get the item that is currently being dragged.
+ *
+ * @return {OO.ui.DraggableElement|null} The currently dragged item, or `null` if no item is being dragged
+ */
+OO.ui.DraggableGroupElement.prototype.getDragItem = function () {
+ return this.dragItem;
+};
+
+/**
+ * Check if an item in the group is currently being dragged.
+ *
+ * @return {Boolean} Item is being dragged
+ */
+OO.ui.DraggableGroupElement.prototype.isDragging = function () {
+ return this.getDragItem() !== null;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/FlaggedElement.js b/vendor/oojs/oojs-ui/src/elements/FlaggedElement.js
new file mode 100644
index 00000000..7050f696
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/FlaggedElement.js
@@ -0,0 +1,209 @@
+/**
+ * The FlaggedElement class is an attribute mixin, meaning that it is used to add
+ * additional functionality to an element created by another class. The class provides
+ * a ‘flags’ property assigned the name (or an array of names) of styling flags,
+ * which are used to customize the look and feel of a widget to better describe its
+ * importance and functionality.
+ *
+ * The library currently contains the following styling flags for general use:
+ *
+ * - **progressive**: Progressive styling is applied to convey that the widget will move the user forward in a process.
+ * - **destructive**: Destructive styling is applied to convey that the widget will remove something.
+ * - **constructive**: Constructive styling is applied to convey that the widget will create something.
+ *
+ * The flags affect the appearance of the buttons:
+ *
+ * @example
+ * // FlaggedElement is mixed into ButtonWidget to provide styling flags
+ * var button1 = new OO.ui.ButtonWidget( {
+ * label: 'Constructive',
+ * flags: 'constructive'
+ * } );
+ * var button2 = new OO.ui.ButtonWidget( {
+ * label: 'Destructive',
+ * flags: 'destructive'
+ * } );
+ * var button3 = new OO.ui.ButtonWidget( {
+ * label: 'Progressive',
+ * flags: 'progressive'
+ * } );
+ * $( 'body' ).append( button1.$element, button2.$element, button3.$element );
+ *
+ * {@link OO.ui.ActionWidget ActionWidgets}, which are a special kind of button that execute an action, use these flags: **primary** and **safe**.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Flagged
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string|string[]} [flags] The name or names of the flags (e.g., 'constructive' or 'primary') to apply.
+ * Please see the [OOjs UI documentation on MediaWiki] [2] for more information about available flags.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Flagged
+ * @cfg {jQuery} [$flagged] The flagged element. By default,
+ * the flagged functionality is applied to the element created by the class ($element).
+ * If a different element is specified, the flagged functionality will be applied to it instead.
+ */
+OO.ui.FlaggedElement = function OoUiFlaggedElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.flags = {};
+ this.$flagged = null;
+
+ // Initialization
+ this.setFlags( config.flags );
+ this.setFlaggedElement( config.$flagged || this.$element );
+};
+
+/* Events */
+
+/**
+ * @event flag
+ * A flag event is emitted when the #clearFlags or #setFlags methods are used. The `changes`
+ * parameter contains the name of each modified flag and indicates whether it was
+ * added or removed.
+ *
+ * @param {Object.<string,boolean>} changes Object keyed by flag name. A Boolean `true` indicates
+ * that the flag was added, `false` that the flag was removed.
+ */
+
+/* Methods */
+
+/**
+ * Set the flagged element.
+ *
+ * This method is used to retarget a flagged mixin so that its functionality applies to the specified element.
+ * If an element is already set, the method will remove the mixin’s effect on that element.
+ *
+ * @param {jQuery} $flagged Element that should be flagged
+ */
+OO.ui.FlaggedElement.prototype.setFlaggedElement = function ( $flagged ) {
+ var classNames = Object.keys( this.flags ).map( function ( flag ) {
+ return 'oo-ui-flaggedElement-' + flag;
+ } ).join( ' ' );
+
+ if ( this.$flagged ) {
+ this.$flagged.removeClass( classNames );
+ }
+
+ this.$flagged = $flagged.addClass( classNames );
+};
+
+/**
+ * Check if the specified flag is set.
+ *
+ * @param {string} flag Name of flag
+ * @return {boolean} The flag is set
+ */
+OO.ui.FlaggedElement.prototype.hasFlag = function ( flag ) {
+ return flag in this.flags;
+};
+
+/**
+ * Get the names of all flags set.
+ *
+ * @return {string[]} Flag names
+ */
+OO.ui.FlaggedElement.prototype.getFlags = function () {
+ return Object.keys( this.flags );
+};
+
+/**
+ * Clear all flags.
+ *
+ * @chainable
+ * @fires flag
+ */
+OO.ui.FlaggedElement.prototype.clearFlags = function () {
+ var flag, className,
+ changes = {},
+ remove = [],
+ classPrefix = 'oo-ui-flaggedElement-';
+
+ for ( flag in this.flags ) {
+ className = classPrefix + flag;
+ changes[ flag ] = false;
+ delete this.flags[ flag ];
+ remove.push( className );
+ }
+
+ if ( this.$flagged ) {
+ this.$flagged.removeClass( remove.join( ' ' ) );
+ }
+
+ this.updateThemeClasses();
+ this.emit( 'flag', changes );
+
+ return this;
+};
+
+/**
+ * Add one or more flags.
+ *
+ * @param {string|string[]|Object.<string, boolean>} flags A flag name, an array of flag names,
+ * or an object keyed by flag name with a boolean value that indicates whether the flag should
+ * be added (`true`) or removed (`false`).
+ * @chainable
+ * @fires flag
+ */
+OO.ui.FlaggedElement.prototype.setFlags = function ( flags ) {
+ var i, len, flag, className,
+ changes = {},
+ add = [],
+ remove = [],
+ classPrefix = 'oo-ui-flaggedElement-';
+
+ if ( typeof flags === 'string' ) {
+ className = classPrefix + flags;
+ // Set
+ if ( !this.flags[ flags ] ) {
+ this.flags[ flags ] = true;
+ add.push( className );
+ }
+ } else if ( Array.isArray( flags ) ) {
+ for ( i = 0, len = flags.length; i < len; i++ ) {
+ flag = flags[ i ];
+ className = classPrefix + flag;
+ // Set
+ if ( !this.flags[ flag ] ) {
+ changes[ flag ] = true;
+ this.flags[ flag ] = true;
+ add.push( className );
+ }
+ }
+ } else if ( OO.isPlainObject( flags ) ) {
+ for ( flag in flags ) {
+ className = classPrefix + flag;
+ if ( flags[ flag ] ) {
+ // Set
+ if ( !this.flags[ flag ] ) {
+ changes[ flag ] = true;
+ this.flags[ flag ] = true;
+ add.push( className );
+ }
+ } else {
+ // Remove
+ if ( this.flags[ flag ] ) {
+ changes[ flag ] = false;
+ delete this.flags[ flag ];
+ remove.push( className );
+ }
+ }
+ }
+ }
+
+ if ( this.$flagged ) {
+ this.$flagged
+ .addClass( add.join( ' ' ) )
+ .removeClass( remove.join( ' ' ) );
+ }
+
+ this.updateThemeClasses();
+ this.emit( 'flag', changes );
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/GroupElement.js b/vendor/oojs/oojs-ui/src/elements/GroupElement.js
new file mode 100644
index 00000000..51cf5d25
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/GroupElement.js
@@ -0,0 +1,290 @@
+/**
+ * Any OOjs UI widget that contains other widgets (such as {@link OO.ui.ButtonWidget buttons} or
+ * {@link OO.ui.OptionWidget options}) mixes in GroupElement. Adding, removing, and clearing
+ * items from the group is done through the interface the class provides.
+ * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Elements/Groups
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$group] The container element created by the class. If this configuration
+ * is omitted, the group element will use a generated `<div>`.
+ */
+OO.ui.GroupElement = function OoUiGroupElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$group = null;
+ this.items = [];
+ this.aggregateItemEvents = {};
+
+ // Initialization
+ this.setGroupElement( config.$group || $( '<div>' ) );
+};
+
+/* Methods */
+
+/**
+ * Set the group element.
+ *
+ * If an element is already set, items will be moved to the new element.
+ *
+ * @param {jQuery} $group Element to use as group
+ */
+OO.ui.GroupElement.prototype.setGroupElement = function ( $group ) {
+ var i, len;
+
+ this.$group = $group;
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ this.$group.append( this.items[ i ].$element );
+ }
+};
+
+/**
+ * Check if a group contains no items.
+ *
+ * @return {boolean} Group is empty
+ */
+OO.ui.GroupElement.prototype.isEmpty = function () {
+ return !this.items.length;
+};
+
+/**
+ * Get all items in the group.
+ *
+ * The method returns an array of item references (e.g., [button1, button2, button3]) and is useful
+ * when synchronizing groups of items, or whenever the references are required (e.g., when removing items
+ * from a group).
+ *
+ * @return {OO.ui.Element[]} An array of items.
+ */
+OO.ui.GroupElement.prototype.getItems = function () {
+ return this.items.slice( 0 );
+};
+
+/**
+ * Get an item by its data.
+ *
+ * Only the first item with matching data will be returned. To return all matching items,
+ * use the #getItemsFromData method.
+ *
+ * @param {Object} data Item data to search for
+ * @return {OO.ui.Element|null} Item with equivalent data, `null` if none exists
+ */
+OO.ui.GroupElement.prototype.getItemFromData = function ( data ) {
+ var i, len, item,
+ hash = OO.getHash( data );
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ item = this.items[ i ];
+ if ( hash === OO.getHash( item.getData() ) ) {
+ return item;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Get items by their data.
+ *
+ * All items with matching data will be returned. To return only the first match, use the #getItemFromData method instead.
+ *
+ * @param {Object} data Item data to search for
+ * @return {OO.ui.Element[]} Items with equivalent data
+ */
+OO.ui.GroupElement.prototype.getItemsFromData = function ( data ) {
+ var i, len, item,
+ hash = OO.getHash( data ),
+ items = [];
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ item = this.items[ i ];
+ if ( hash === OO.getHash( item.getData() ) ) {
+ items.push( item );
+ }
+ }
+
+ return items;
+};
+
+/**
+ * Aggregate the events emitted by the group.
+ *
+ * When events are aggregated, the group will listen to all contained items for the event,
+ * and then emit the event under a new name. The new event will contain an additional leading
+ * parameter containing the item that emitted the original event. Other arguments emitted from
+ * the original event are passed through.
+ *
+ * @param {Object.<string,string|null>} events An object keyed by the name of the event that should be
+ * aggregated (e.g., ‘click’) and the value of the new name to use (e.g., ‘groupClick’).
+ * A `null` value will remove aggregated events.
+
+ * @throws {Error} An error is thrown if aggregation already exists.
+ */
+OO.ui.GroupElement.prototype.aggregate = function ( events ) {
+ var i, len, item, add, remove, itemEvent, groupEvent;
+
+ for ( itemEvent in events ) {
+ groupEvent = events[ itemEvent ];
+
+ // Remove existing aggregated event
+ if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
+ // Don't allow duplicate aggregations
+ if ( groupEvent ) {
+ throw new Error( 'Duplicate item event aggregation for ' + itemEvent );
+ }
+ // Remove event aggregation from existing items
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ item = this.items[ i ];
+ if ( item.connect && item.disconnect ) {
+ remove = {};
+ remove[ itemEvent ] = [ 'emit', groupEvent, item ];
+ item.disconnect( this, remove );
+ }
+ }
+ // Prevent future items from aggregating event
+ delete this.aggregateItemEvents[ itemEvent ];
+ }
+
+ // Add new aggregate event
+ if ( groupEvent ) {
+ // Make future items aggregate event
+ this.aggregateItemEvents[ itemEvent ] = groupEvent;
+ // Add event aggregation to existing items
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ item = this.items[ i ];
+ if ( item.connect && item.disconnect ) {
+ add = {};
+ add[ itemEvent ] = [ 'emit', groupEvent, item ];
+ item.connect( this, add );
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Add items to the group.
+ *
+ * Items will be added to the end of the group array unless the optional `index` parameter specifies
+ * a different insertion point. Adding an existing item will move it to the end of the array or the point specified by the `index`.
+ *
+ * @param {OO.ui.Element[]} items An array of items to add to the group
+ * @param {number} [index] Index of the insertion point
+ * @chainable
+ */
+OO.ui.GroupElement.prototype.addItems = function ( items, index ) {
+ var i, len, item, event, events, currentIndex,
+ itemElements = [];
+
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ item = items[ i ];
+
+ // Check if item exists then remove it first, effectively "moving" it
+ currentIndex = $.inArray( item, this.items );
+ if ( currentIndex >= 0 ) {
+ this.removeItems( [ item ] );
+ // Adjust index to compensate for removal
+ if ( currentIndex < index ) {
+ index--;
+ }
+ }
+ // Add the item
+ if ( item.connect && item.disconnect && !$.isEmptyObject( this.aggregateItemEvents ) ) {
+ events = {};
+ for ( event in this.aggregateItemEvents ) {
+ events[ event ] = [ 'emit', this.aggregateItemEvents[ event ], item ];
+ }
+ item.connect( this, events );
+ }
+ item.setElementGroup( this );
+ itemElements.push( item.$element.get( 0 ) );
+ }
+
+ if ( index === undefined || index < 0 || index >= this.items.length ) {
+ this.$group.append( itemElements );
+ this.items.push.apply( this.items, items );
+ } else if ( index === 0 ) {
+ this.$group.prepend( itemElements );
+ this.items.unshift.apply( this.items, items );
+ } else {
+ this.items[ index ].$element.before( itemElements );
+ this.items.splice.apply( this.items, [ index, 0 ].concat( items ) );
+ }
+
+ return this;
+};
+
+/**
+ * Remove the specified items from a group.
+ *
+ * Removed items are detached (not removed) from the DOM so that they may be reused.
+ * To remove all items from a group, you may wish to use the #clearItems method instead.
+ *
+ * @param {OO.ui.Element[]} items An array of items to remove
+ * @chainable
+ */
+OO.ui.GroupElement.prototype.removeItems = function ( items ) {
+ var i, len, item, index, remove, itemEvent;
+
+ // Remove specific items
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ item = items[ i ];
+ index = $.inArray( item, this.items );
+ if ( index !== -1 ) {
+ if (
+ item.connect && item.disconnect &&
+ !$.isEmptyObject( this.aggregateItemEvents )
+ ) {
+ remove = {};
+ if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
+ remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
+ }
+ item.disconnect( this, remove );
+ }
+ item.setElementGroup( null );
+ this.items.splice( index, 1 );
+ item.$element.detach();
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Clear all items from the group.
+ *
+ * Cleared items are detached from the DOM, not removed, so that they may be reused.
+ * To remove only a subset of items from a group, use the #removeItems method.
+ *
+ * @chainable
+ */
+OO.ui.GroupElement.prototype.clearItems = function () {
+ var i, len, item, remove, itemEvent;
+
+ // Remove all items
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ item = this.items[ i ];
+ if (
+ item.connect && item.disconnect &&
+ !$.isEmptyObject( this.aggregateItemEvents )
+ ) {
+ remove = {};
+ if ( Object.prototype.hasOwnProperty.call( this.aggregateItemEvents, itemEvent ) ) {
+ remove[ itemEvent ] = [ 'emit', this.aggregateItemEvents[ itemEvent ], item ];
+ }
+ item.disconnect( this, remove );
+ }
+ item.setElementGroup( null );
+ item.$element.detach();
+ }
+
+ this.items = [];
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/IconElement.js b/vendor/oojs/oojs-ui/src/elements/IconElement.js
new file mode 100644
index 00000000..e3cf2f2a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/IconElement.js
@@ -0,0 +1,187 @@
+/**
+ * IconElement is often mixed into other classes to generate an icon.
+ * Icons are graphics, about the size of normal text. They are used to aid the user
+ * in locating a control or to convey information in a space-efficient way. See the
+ * [OOjs UI documentation on MediaWiki] [1] for a list of icons
+ * included in the library.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$icon] The icon element created by the class. If this configuration is omitted,
+ * the icon element will use a generated `<span>`. To use a different HTML tag, or to specify that
+ * the icon element be set to an existing icon instead of the one generated by this class, set a
+ * value using a jQuery selection. For example:
+ *
+ * // Use a <div> tag instead of a <span>
+ * $icon: $("<div>")
+ * // Use an existing icon element instead of the one generated by the class
+ * $icon: this.$element
+ * // Use an icon element from a child widget
+ * $icon: this.childwidget.$element
+ * @cfg {Object|string} [icon=''] The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of
+ * symbolic names. A map is used for i18n purposes and contains a `default` icon
+ * name and additional names keyed by language code. The `default` name is used when no icon is keyed
+ * by the user's language.
+ *
+ * Example of an i18n map:
+ *
+ * { default: 'bold-a', en: 'bold-b', de: 'bold-f' }
+ * See the [OOjs UI documentation on MediaWiki] [2] for a list of icons included in the library.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
+ * @cfg {string|Function} [iconTitle] A text string used as the icon title, or a function that returns title
+ * text. The icon title is displayed when users move the mouse over the icon.
+ */
+OO.ui.IconElement = function OoUiIconElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$icon = null;
+ this.icon = null;
+ this.iconTitle = null;
+
+ // Initialization
+ this.setIcon( config.icon || this.constructor.static.icon );
+ this.setIconTitle( config.iconTitle || this.constructor.static.iconTitle );
+ this.setIconElement( config.$icon || $( '<span>' ) );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.IconElement );
+
+/* Static Properties */
+
+/**
+ * The symbolic name of the icon (e.g., ‘remove’ or ‘menu’), or a map of symbolic names. A map is used
+ * for i18n purposes and contains a `default` icon name and additional names keyed by
+ * language code. The `default` name is used when no icon is keyed by the user's language.
+ *
+ * Example of an i18n map:
+ *
+ * { default: 'bold-a', en: 'bold-b', de: 'bold-f' }
+ *
+ * Note: the static property will be overridden if the #icon configuration is used.
+ *
+ * @static
+ * @inheritable
+ * @property {Object|string}
+ */
+OO.ui.IconElement.static.icon = null;
+
+/**
+ * The icon title, displayed when users move the mouse over the icon. The value can be text, a
+ * function that returns title text, or `null` for no title.
+ *
+ * The static property will be overridden if the #iconTitle configuration is used.
+ *
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
+ */
+OO.ui.IconElement.static.iconTitle = null;
+
+/* Methods */
+
+/**
+ * Set the icon element. This method is used to retarget an icon mixin so that its functionality
+ * applies to the specified icon element instead of the one created by the class. If an icon
+ * element is already set, the mixin’s effect on that element is removed. Generated CSS classes
+ * and mixin methods will no longer affect the element.
+ *
+ * @param {jQuery} $icon Element to use as icon
+ */
+OO.ui.IconElement.prototype.setIconElement = function ( $icon ) {
+ if ( this.$icon ) {
+ this.$icon
+ .removeClass( 'oo-ui-iconElement-icon oo-ui-icon-' + this.icon )
+ .removeAttr( 'title' );
+ }
+
+ this.$icon = $icon
+ .addClass( 'oo-ui-iconElement-icon' )
+ .toggleClass( 'oo-ui-icon-' + this.icon, !!this.icon );
+ if ( this.iconTitle !== null ) {
+ this.$icon.attr( 'title', this.iconTitle );
+ }
+};
+
+/**
+ * Set icon by symbolic name (e.g., ‘remove’ or ‘menu’). Use `null` to remove an icon.
+ * The icon parameter can also be set to a map of icon names. See the #icon config setting
+ * for an example.
+ *
+ * @param {Object|string|null} icon A symbolic icon name, a {@link #icon map of icon names} keyed
+ * by language code, or `null` to remove the icon.
+ * @chainable
+ */
+OO.ui.IconElement.prototype.setIcon = function ( icon ) {
+ icon = OO.isPlainObject( icon ) ? OO.ui.getLocalValue( icon, null, 'default' ) : icon;
+ icon = typeof icon === 'string' && icon.trim().length ? icon.trim() : null;
+
+ if ( this.icon !== icon ) {
+ if ( this.$icon ) {
+ if ( this.icon !== null ) {
+ this.$icon.removeClass( 'oo-ui-icon-' + this.icon );
+ }
+ if ( icon !== null ) {
+ this.$icon.addClass( 'oo-ui-icon-' + icon );
+ }
+ }
+ this.icon = icon;
+ }
+
+ this.$element.toggleClass( 'oo-ui-iconElement', !!this.icon );
+ this.updateThemeClasses();
+
+ return this;
+};
+
+/**
+ * Set the icon title. Use `null` to remove the title.
+ *
+ * @param {string|Function|null} iconTitle A text string used as the icon title,
+ * a function that returns title text, or `null` for no title.
+ * @chainable
+ */
+OO.ui.IconElement.prototype.setIconTitle = function ( iconTitle ) {
+ iconTitle = typeof iconTitle === 'function' ||
+ ( typeof iconTitle === 'string' && iconTitle.length ) ?
+ OO.ui.resolveMsg( iconTitle ) : null;
+
+ if ( this.iconTitle !== iconTitle ) {
+ this.iconTitle = iconTitle;
+ if ( this.$icon ) {
+ if ( this.iconTitle !== null ) {
+ this.$icon.attr( 'title', iconTitle );
+ } else {
+ this.$icon.removeAttr( 'title' );
+ }
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Get the symbolic name of the icon.
+ *
+ * @return {string} Icon name
+ */
+OO.ui.IconElement.prototype.getIcon = function () {
+ return this.icon;
+};
+
+/**
+ * Get the icon title. The title text is displayed when a user moves the mouse over the icon.
+ *
+ * @return {string} Icon title text
+ */
+OO.ui.IconElement.prototype.getIconTitle = function () {
+ return this.iconTitle;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/IndicatorElement.js b/vendor/oojs/oojs-ui/src/elements/IndicatorElement.js
new file mode 100644
index 00000000..5c6294d2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/IndicatorElement.js
@@ -0,0 +1,168 @@
+/**
+ * IndicatorElement is often mixed into other classes to generate an indicator.
+ * Indicators are small graphics that are generally used in two ways:
+ *
+ * - To draw attention to the status of an item. For example, an indicator might be
+ * used to show that an item in a list has errors that need to be resolved.
+ * - To clarify the function of a control that acts in an exceptional way (a button
+ * that opens a menu instead of performing an action directly, for example).
+ *
+ * For a list of indicators included in the library, please see the
+ * [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$indicator] The indicator element created by the class. If this
+ * configuration is omitted, the indicator element will use a generated `<span>`.
+ * @cfg {string} [indicator] Symbolic name of the indicator (e.g., ‘alert’ or ‘down’).
+ * See the [OOjs UI documentation on MediaWiki][2] for a list of indicators included
+ * in the library.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
+ * @cfg {string|Function} [indicatorTitle] A text string used as the indicator title,
+ * or a function that returns title text. The indicator title is displayed when users move
+ * the mouse over the indicator.
+ */
+OO.ui.IndicatorElement = function OoUiIndicatorElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$indicator = null;
+ this.indicator = null;
+ this.indicatorTitle = null;
+
+ // Initialization
+ this.setIndicator( config.indicator || this.constructor.static.indicator );
+ this.setIndicatorTitle( config.indicatorTitle || this.constructor.static.indicatorTitle );
+ this.setIndicatorElement( config.$indicator || $( '<span>' ) );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.IndicatorElement );
+
+/* Static Properties */
+
+/**
+ * Symbolic name of the indicator (e.g., ‘alert’ or ‘down’).
+ * The static property will be overridden if the #indicator configuration is used.
+ *
+ * @static
+ * @inheritable
+ * @property {string|null}
+ */
+OO.ui.IndicatorElement.static.indicator = null;
+
+/**
+ * A text string used as the indicator title, a function that returns title text, or `null`
+ * for no title. The static property will be overridden if the #indicatorTitle configuration is used.
+ *
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
+ */
+OO.ui.IndicatorElement.static.indicatorTitle = null;
+
+/* Methods */
+
+/**
+ * Set the indicator element.
+ *
+ * If an element is already set, it will be cleaned up before setting up the new element.
+ *
+ * @param {jQuery} $indicator Element to use as indicator
+ */
+OO.ui.IndicatorElement.prototype.setIndicatorElement = function ( $indicator ) {
+ if ( this.$indicator ) {
+ this.$indicator
+ .removeClass( 'oo-ui-indicatorElement-indicator oo-ui-indicator-' + this.indicator )
+ .removeAttr( 'title' );
+ }
+
+ this.$indicator = $indicator
+ .addClass( 'oo-ui-indicatorElement-indicator' )
+ .toggleClass( 'oo-ui-indicator-' + this.indicator, !!this.indicator );
+ if ( this.indicatorTitle !== null ) {
+ this.$indicator.attr( 'title', this.indicatorTitle );
+ }
+};
+
+/**
+ * Set the indicator by its symbolic name: ‘alert’, ‘down’, ‘next’, ‘previous’, ‘required’, ‘up’. Use `null` to remove the indicator.
+ *
+ * @param {string|null} indicator Symbolic name of indicator, or `null` for no indicator
+ * @chainable
+ */
+OO.ui.IndicatorElement.prototype.setIndicator = function ( indicator ) {
+ indicator = typeof indicator === 'string' && indicator.length ? indicator.trim() : null;
+
+ if ( this.indicator !== indicator ) {
+ if ( this.$indicator ) {
+ if ( this.indicator !== null ) {
+ this.$indicator.removeClass( 'oo-ui-indicator-' + this.indicator );
+ }
+ if ( indicator !== null ) {
+ this.$indicator.addClass( 'oo-ui-indicator-' + indicator );
+ }
+ }
+ this.indicator = indicator;
+ }
+
+ this.$element.toggleClass( 'oo-ui-indicatorElement', !!this.indicator );
+ this.updateThemeClasses();
+
+ return this;
+};
+
+/**
+ * Set the indicator title.
+ *
+ * The title is displayed when a user moves the mouse over the indicator.
+ *
+ * @param {string|Function|null} indicator Indicator title text, a function that returns text, or
+ * `null` for no indicator title
+ * @chainable
+ */
+OO.ui.IndicatorElement.prototype.setIndicatorTitle = function ( indicatorTitle ) {
+ indicatorTitle = typeof indicatorTitle === 'function' ||
+ ( typeof indicatorTitle === 'string' && indicatorTitle.length ) ?
+ OO.ui.resolveMsg( indicatorTitle ) : null;
+
+ if ( this.indicatorTitle !== indicatorTitle ) {
+ this.indicatorTitle = indicatorTitle;
+ if ( this.$indicator ) {
+ if ( this.indicatorTitle !== null ) {
+ this.$indicator.attr( 'title', indicatorTitle );
+ } else {
+ this.$indicator.removeAttr( 'title' );
+ }
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Get the symbolic name of the indicator (e.g., ‘alert’ or ‘down’).
+ *
+ * @return {string} Symbolic name of indicator
+ */
+OO.ui.IndicatorElement.prototype.getIndicator = function () {
+ return this.indicator;
+};
+
+/**
+ * Get the indicator title.
+ *
+ * The title is displayed when a user moves the mouse over the indicator.
+ *
+ * @return {string} Indicator title text
+ */
+OO.ui.IndicatorElement.prototype.getIndicatorTitle = function () {
+ return this.indicatorTitle;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/LabelElement.js b/vendor/oojs/oojs-ui/src/elements/LabelElement.js
new file mode 100644
index 00000000..674fa73a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/LabelElement.js
@@ -0,0 +1,152 @@
+/**
+ * LabelElement is often mixed into other classes to generate a label, which
+ * helps identify the function of an interface element.
+ * See the [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Labels
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$label] The label element created by the class. If this
+ * configuration is omitted, the label element will use a generated `<span>`.
+ * @cfg {jQuery|string|Function|OO.ui.HtmlSnippet} [label] The label text. The label can be specified
+ * as a plaintext string, a jQuery selection of elements, or a function that will produce a string
+ * in the future. See the [OOjs UI documentation on MediaWiki] [2] for examples.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Labels
+ * @cfg {boolean} [autoFitLabel=true] Fit the label to the width of the parent element.
+ * The label will be truncated to fit if necessary.
+ */
+OO.ui.LabelElement = function OoUiLabelElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$label = null;
+ this.label = null;
+ this.autoFitLabel = config.autoFitLabel === undefined || !!config.autoFitLabel;
+
+ // Initialization
+ this.setLabel( config.label || this.constructor.static.label );
+ this.setLabelElement( config.$label || $( '<span>' ) );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.LabelElement );
+
+/* Events */
+
+/**
+ * @event labelChange
+ * @param {string} value
+ */
+
+/* Static Properties */
+
+/**
+ * The label text. The label can be specified as a plaintext string, a function that will
+ * produce a string in the future, or `null` for no label. The static value will
+ * be overridden if a label is specified with the #label config option.
+ *
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
+ */
+OO.ui.LabelElement.static.label = null;
+
+/* Methods */
+
+/**
+ * Set the label element.
+ *
+ * If an element is already set, it will be cleaned up before setting up the new element.
+ *
+ * @param {jQuery} $label Element to use as label
+ */
+OO.ui.LabelElement.prototype.setLabelElement = function ( $label ) {
+ if ( this.$label ) {
+ this.$label.removeClass( 'oo-ui-labelElement-label' ).empty();
+ }
+
+ this.$label = $label.addClass( 'oo-ui-labelElement-label' );
+ this.setLabelContent( this.label );
+};
+
+/**
+ * Set the label.
+ *
+ * An empty string will result in the label being hidden. A string containing only whitespace will
+ * be converted to a single `&nbsp;`.
+ *
+ * @param {jQuery|string|OO.ui.HtmlSnippet|Function|null} label Label nodes; text; a function that returns nodes or
+ * text; or null for no label
+ * @chainable
+ */
+OO.ui.LabelElement.prototype.setLabel = function ( label ) {
+ label = typeof label === 'function' ? OO.ui.resolveMsg( label ) : label;
+ label = ( ( typeof label === 'string' && label.length ) || label instanceof jQuery || label instanceof OO.ui.HtmlSnippet ) ? label : null;
+
+ this.$element.toggleClass( 'oo-ui-labelElement', !!label );
+
+ if ( this.label !== label ) {
+ if ( this.$label ) {
+ this.setLabelContent( label );
+ }
+ this.label = label;
+ this.emit( 'labelChange' );
+ }
+
+ return this;
+};
+
+/**
+ * Get the label.
+ *
+ * @return {jQuery|string|Function|null} Label nodes; text; a function that returns nodes or
+ * text; or null for no label
+ */
+OO.ui.LabelElement.prototype.getLabel = function () {
+ return this.label;
+};
+
+/**
+ * Fit the label.
+ *
+ * @chainable
+ */
+OO.ui.LabelElement.prototype.fitLabel = function () {
+ if ( this.$label && this.$label.autoEllipsis && this.autoFitLabel ) {
+ this.$label.autoEllipsis( { hasSpan: false, tooltip: true } );
+ }
+
+ return this;
+};
+
+/**
+ * Set the content of the label.
+ *
+ * Do not call this method until after the label element has been set by #setLabelElement.
+ *
+ * @private
+ * @param {jQuery|string|Function|null} label Label nodes; text; a function that returns nodes or
+ * text; or null for no label
+ */
+OO.ui.LabelElement.prototype.setLabelContent = function ( label ) {
+ if ( typeof label === 'string' ) {
+ if ( label.match( /^\s*$/ ) ) {
+ // Convert whitespace only string to a single non-breaking space
+ this.$label.html( '&nbsp;' );
+ } else {
+ this.$label.text( label );
+ }
+ } else if ( label instanceof OO.ui.HtmlSnippet ) {
+ this.$label.html( label.toString() );
+ } else if ( label instanceof jQuery ) {
+ this.$label.empty().append( label );
+ } else {
+ this.$label.empty();
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/LookupElement.js b/vendor/oojs/oojs-ui/src/elements/LookupElement.js
new file mode 100644
index 00000000..b79f02a9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/LookupElement.js
@@ -0,0 +1,352 @@
+/**
+ * LookupElement is a mixin that creates a {@link OO.ui.TextInputMenuSelectWidget menu} of suggested values for
+ * a {@link OO.ui.TextInputWidget text input widget}. Suggested values are based on the characters the user types
+ * into the text input field and, in general, the menu is only displayed when the user types. If a suggested value is chosen
+ * from the lookup menu, that value becomes the value of the input field.
+ *
+ * Note that a new menu of suggested items is displayed when a value is chosen from the lookup menu. If this is
+ * not the desired behavior, disable lookup menus with the #setLookupsDisabled method, then set the value, then
+ * re-enable lookups.
+ *
+ * See the [OOjs UI demos][1] for an example.
+ *
+ * [1]: https://tools.wmflabs.org/oojs-ui/oojs-ui/demos/index.html#widgets-apex-vector-ltr
+ *
+ * @class
+ * @abstract
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$overlay] Overlay for the lookup menu; defaults to relative positioning
+ * @cfg {jQuery} [$container=this.$element] The container element. The lookup menu is rendered beneath the specified element.
+ * @cfg {boolean} [allowSuggestionsWhenEmpty=false] Request and display a lookup menu when the text input is empty.
+ * By default, the lookup menu is not generated and displayed until the user begins to type.
+ */
+OO.ui.LookupElement = function OoUiLookupElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$overlay = config.$overlay || this.$element;
+ this.lookupMenu = new OO.ui.TextInputMenuSelectWidget( this, {
+ widget: this,
+ input: this,
+ $container: config.$container
+ } );
+
+ this.allowSuggestionsWhenEmpty = config.allowSuggestionsWhenEmpty || false;
+
+ this.lookupCache = {};
+ this.lookupQuery = null;
+ this.lookupRequest = null;
+ this.lookupsDisabled = false;
+ this.lookupInputFocused = false;
+
+ // Events
+ this.$input.on( {
+ focus: this.onLookupInputFocus.bind( this ),
+ blur: this.onLookupInputBlur.bind( this ),
+ mousedown: this.onLookupInputMouseDown.bind( this )
+ } );
+ this.connect( this, { change: 'onLookupInputChange' } );
+ this.lookupMenu.connect( this, {
+ toggle: 'onLookupMenuToggle',
+ choose: 'onLookupMenuItemChoose'
+ } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-lookupElement' );
+ this.lookupMenu.$element.addClass( 'oo-ui-lookupElement-menu' );
+ this.$overlay.append( this.lookupMenu.$element );
+};
+
+/* Methods */
+
+/**
+ * Handle input focus event.
+ *
+ * @protected
+ * @param {jQuery.Event} e Input focus event
+ */
+OO.ui.LookupElement.prototype.onLookupInputFocus = function () {
+ this.lookupInputFocused = true;
+ this.populateLookupMenu();
+};
+
+/**
+ * Handle input blur event.
+ *
+ * @protected
+ * @param {jQuery.Event} e Input blur event
+ */
+OO.ui.LookupElement.prototype.onLookupInputBlur = function () {
+ this.closeLookupMenu();
+ this.lookupInputFocused = false;
+};
+
+/**
+ * Handle input mouse down event.
+ *
+ * @protected
+ * @param {jQuery.Event} e Input mouse down event
+ */
+OO.ui.LookupElement.prototype.onLookupInputMouseDown = function () {
+ // Only open the menu if the input was already focused.
+ // This way we allow the user to open the menu again after closing it with Esc
+ // by clicking in the input. Opening (and populating) the menu when initially
+ // clicking into the input is handled by the focus handler.
+ if ( this.lookupInputFocused && !this.lookupMenu.isVisible() ) {
+ this.populateLookupMenu();
+ }
+};
+
+/**
+ * Handle input change event.
+ *
+ * @protected
+ * @param {string} value New input value
+ */
+OO.ui.LookupElement.prototype.onLookupInputChange = function () {
+ if ( this.lookupInputFocused ) {
+ this.populateLookupMenu();
+ }
+};
+
+/**
+ * Handle the lookup menu being shown/hidden.
+ *
+ * @protected
+ * @param {boolean} visible Whether the lookup menu is now visible.
+ */
+OO.ui.LookupElement.prototype.onLookupMenuToggle = function ( visible ) {
+ if ( !visible ) {
+ // When the menu is hidden, abort any active request and clear the menu.
+ // This has to be done here in addition to closeLookupMenu(), because
+ // MenuSelectWidget will close itself when the user presses Esc.
+ this.abortLookupRequest();
+ this.lookupMenu.clearItems();
+ }
+};
+
+/**
+ * Handle menu item 'choose' event, updating the text input value to the value of the clicked item.
+ *
+ * @protected
+ * @param {OO.ui.MenuOptionWidget} item Selected item
+ */
+OO.ui.LookupElement.prototype.onLookupMenuItemChoose = function ( item ) {
+ this.setValue( item.getData() );
+};
+
+/**
+ * Get lookup menu.
+ *
+ * @private
+ * @return {OO.ui.TextInputMenuSelectWidget}
+ */
+OO.ui.LookupElement.prototype.getLookupMenu = function () {
+ return this.lookupMenu;
+};
+
+/**
+ * Disable or re-enable lookups.
+ *
+ * When lookups are disabled, calls to #populateLookupMenu will be ignored.
+ *
+ * @param {boolean} disabled Disable lookups
+ */
+OO.ui.LookupElement.prototype.setLookupsDisabled = function ( disabled ) {
+ this.lookupsDisabled = !!disabled;
+};
+
+/**
+ * Open the menu. If there are no entries in the menu, this does nothing.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.LookupElement.prototype.openLookupMenu = function () {
+ if ( !this.lookupMenu.isEmpty() ) {
+ this.lookupMenu.toggle( true );
+ }
+ return this;
+};
+
+/**
+ * Close the menu, empty it, and abort any pending request.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.LookupElement.prototype.closeLookupMenu = function () {
+ this.lookupMenu.toggle( false );
+ this.abortLookupRequest();
+ this.lookupMenu.clearItems();
+ return this;
+};
+
+/**
+ * Request menu items based on the input's current value, and when they arrive,
+ * populate the menu with these items and show the menu.
+ *
+ * If lookups have been disabled with #setLookupsDisabled, this function does nothing.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.LookupElement.prototype.populateLookupMenu = function () {
+ var widget = this,
+ value = this.getValue();
+
+ if ( this.lookupsDisabled ) {
+ return;
+ }
+
+ // If the input is empty, clear the menu, unless suggestions when empty are allowed.
+ if ( !this.allowSuggestionsWhenEmpty && value === '' ) {
+ this.closeLookupMenu();
+ // Skip population if there is already a request pending for the current value
+ } else if ( value !== this.lookupQuery ) {
+ this.getLookupMenuItems()
+ .done( function ( items ) {
+ widget.lookupMenu.clearItems();
+ if ( items.length ) {
+ widget.lookupMenu
+ .addItems( items )
+ .toggle( true );
+ widget.initializeLookupMenuSelection();
+ } else {
+ widget.lookupMenu.toggle( false );
+ }
+ } )
+ .fail( function () {
+ widget.lookupMenu.clearItems();
+ } );
+ }
+
+ return this;
+};
+
+/**
+ * Highlight the first selectable item in the menu.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.LookupElement.prototype.initializeLookupMenuSelection = function () {
+ if ( !this.lookupMenu.getSelectedItem() ) {
+ this.lookupMenu.highlightItem( this.lookupMenu.getFirstSelectableItem() );
+ }
+};
+
+/**
+ * Get lookup menu items for the current query.
+ *
+ * @private
+ * @return {jQuery.Promise} Promise object which will be passed menu items as the first argument of
+ * the done event. If the request was aborted to make way for a subsequent request, this promise
+ * will not be rejected: it will remain pending forever.
+ */
+OO.ui.LookupElement.prototype.getLookupMenuItems = function () {
+ var widget = this,
+ value = this.getValue(),
+ deferred = $.Deferred(),
+ ourRequest;
+
+ this.abortLookupRequest();
+ if ( Object.prototype.hasOwnProperty.call( this.lookupCache, value ) ) {
+ deferred.resolve( this.getLookupMenuOptionsFromData( this.lookupCache[ value ] ) );
+ } else {
+ this.pushPending();
+ this.lookupQuery = value;
+ ourRequest = this.lookupRequest = this.getLookupRequest();
+ ourRequest
+ .always( function () {
+ // We need to pop pending even if this is an old request, otherwise
+ // the widget will remain pending forever.
+ // TODO: this assumes that an aborted request will fail or succeed soon after
+ // being aborted, or at least eventually. It would be nice if we could popPending()
+ // at abort time, but only if we knew that we hadn't already called popPending()
+ // for that request.
+ widget.popPending();
+ } )
+ .done( function ( response ) {
+ // If this is an old request (and aborting it somehow caused it to still succeed),
+ // ignore its success completely
+ if ( ourRequest === widget.lookupRequest ) {
+ widget.lookupQuery = null;
+ widget.lookupRequest = null;
+ widget.lookupCache[ value ] = widget.getLookupCacheDataFromResponse( response );
+ deferred.resolve( widget.getLookupMenuOptionsFromData( widget.lookupCache[ value ] ) );
+ }
+ } )
+ .fail( function () {
+ // If this is an old request (or a request failing because it's being aborted),
+ // ignore its failure completely
+ if ( ourRequest === widget.lookupRequest ) {
+ widget.lookupQuery = null;
+ widget.lookupRequest = null;
+ deferred.reject();
+ }
+ } );
+ }
+ return deferred.promise();
+};
+
+/**
+ * Abort the currently pending lookup request, if any.
+ *
+ * @private
+ */
+OO.ui.LookupElement.prototype.abortLookupRequest = function () {
+ var oldRequest = this.lookupRequest;
+ if ( oldRequest ) {
+ // First unset this.lookupRequest to the fail handler will notice
+ // that the request is no longer current
+ this.lookupRequest = null;
+ this.lookupQuery = null;
+ oldRequest.abort();
+ }
+};
+
+/**
+ * Get a new request object of the current lookup query value.
+ *
+ * @protected
+ * @abstract
+ * @return {jQuery.Promise} jQuery AJAX object, or promise object with an .abort() method
+ */
+OO.ui.LookupElement.prototype.getLookupRequest = function () {
+ // Stub, implemented in subclass
+ return null;
+};
+
+/**
+ * Pre-process data returned by the request from #getLookupRequest.
+ *
+ * The return value of this function will be cached, and any further queries for the given value
+ * will use the cache rather than doing API requests.
+ *
+ * @protected
+ * @abstract
+ * @param {Mixed} response Response from server
+ * @return {Mixed} Cached result data
+ */
+OO.ui.LookupElement.prototype.getLookupCacheDataFromResponse = function () {
+ // Stub, implemented in subclass
+ return [];
+};
+
+/**
+ * Get a list of menu option widgets from the (possibly cached) data returned by
+ * #getLookupCacheDataFromResponse.
+ *
+ * @protected
+ * @abstract
+ * @param {Mixed} data Cached result data, usually an array
+ * @return {OO.ui.MenuOptionWidget[]} Menu items
+ */
+OO.ui.LookupElement.prototype.getLookupMenuOptionsFromData = function () {
+ // Stub, implemented in subclass
+ return [];
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/PendingElement.js b/vendor/oojs/oojs-ui/src/elements/PendingElement.js
new file mode 100644
index 00000000..c5f71d54
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/PendingElement.js
@@ -0,0 +1,84 @@
+/**
+ * Element that can be marked as pending.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$pending] Element to mark as pending, defaults to this.$element
+ */
+OO.ui.PendingElement = function OoUiPendingElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.pending = 0;
+ this.$pending = null;
+
+ // Initialisation
+ this.setPendingElement( config.$pending || this.$element );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.PendingElement );
+
+/* Methods */
+
+/**
+ * Set the pending element (and clean up any existing one).
+ *
+ * @param {jQuery} $pending The element to set to pending.
+ */
+OO.ui.PendingElement.prototype.setPendingElement = function ( $pending ) {
+ if ( this.$pending ) {
+ this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
+ }
+
+ this.$pending = $pending;
+ if ( this.pending > 0 ) {
+ this.$pending.addClass( 'oo-ui-pendingElement-pending' );
+ }
+};
+
+/**
+ * Check if input is pending.
+ *
+ * @return {boolean}
+ */
+OO.ui.PendingElement.prototype.isPending = function () {
+ return !!this.pending;
+};
+
+/**
+ * Increase the pending stack.
+ *
+ * @chainable
+ */
+OO.ui.PendingElement.prototype.pushPending = function () {
+ if ( this.pending === 0 ) {
+ this.$pending.addClass( 'oo-ui-pendingElement-pending' );
+ this.updateThemeClasses();
+ }
+ this.pending++;
+
+ return this;
+};
+
+/**
+ * Reduce the pending stack.
+ *
+ * Clamped at zero.
+ *
+ * @chainable
+ */
+OO.ui.PendingElement.prototype.popPending = function () {
+ if ( this.pending === 1 ) {
+ this.$pending.removeClass( 'oo-ui-pendingElement-pending' );
+ this.updateThemeClasses();
+ }
+ this.pending = Math.max( 0, this.pending - 1 );
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/PopupElement.js b/vendor/oojs/oojs-ui/src/elements/PopupElement.js
new file mode 100644
index 00000000..099e94b7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/PopupElement.js
@@ -0,0 +1,36 @@
+/**
+ * PopupElement is mixed into other classes to generate a {@link OO.ui.PopupWidget popup widget}.
+ * A popup is a container for content. It is overlaid and positioned absolutely. By default, each
+ * popup has an anchor, which is an arrow-like protrusion that points toward the popup’s origin.
+ * See {@link OO.ui.PopupWidget PopupWidget} for an example.
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [popup] Configuration to pass to popup
+ * @cfg {boolean} [popup.autoClose=true] Popup auto-closes when it loses focus
+ */
+OO.ui.PopupElement = function OoUiPopupElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.popup = new OO.ui.PopupWidget( $.extend(
+ { autoClose: true },
+ config.popup,
+ { $autoCloseIgnore: this.$element }
+ ) );
+};
+
+/* Methods */
+
+/**
+ * Get popup.
+ *
+ * @return {OO.ui.PopupWidget} Popup widget
+ */
+OO.ui.PopupElement.prototype.getPopup = function () {
+ return this.popup;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/TabIndexedElement.js b/vendor/oojs/oojs-ui/src/elements/TabIndexedElement.js
new file mode 100644
index 00000000..5c2151a0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/TabIndexedElement.js
@@ -0,0 +1,138 @@
+/**
+ * The TabIndexedElement class is an attribute mixin used to add additional functionality to an
+ * element created by another class. The mixin provides a ‘tabIndex’ property, which specifies the
+ * order in which users will navigate through the focusable elements via the "tab" key.
+ *
+ * @example
+ * // TabIndexedElement is mixed into the ButtonWidget class
+ * // to provide a tabIndex property.
+ * var button1 = new OO.ui.ButtonWidget( {
+ * label: 'fourth',
+ * tabIndex: 4
+ * } );
+ * var button2 = new OO.ui.ButtonWidget( {
+ * label: 'second',
+ * tabIndex: 2
+ * } );
+ * var button3 = new OO.ui.ButtonWidget( {
+ * label: 'third',
+ * tabIndex: 3
+ * } );
+ * var button4 = new OO.ui.ButtonWidget( {
+ * label: 'first',
+ * tabIndex: 1
+ * } );
+ * $( 'body' ).append( button1.$element, button2.$element, button3.$element, button4.$element );
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$tabIndexed] The element that should use the tabindex functionality. By default,
+ * the functionality is applied to the element created by the class ($element). If a different element is specified, the tabindex
+ * functionality will be applied to it instead.
+ * @cfg {number|null} [tabIndex=0] Number that specifies the element’s position in the tab-navigation
+ * order (e.g., 1 for the first focusable element). Use 0 to use the default navigation order; use -1
+ * to remove the element from the tab-navigation flow.
+ */
+OO.ui.TabIndexedElement = function OoUiTabIndexedElement( config ) {
+ // Configuration initialization
+ config = $.extend( { tabIndex: 0 }, config );
+
+ // Properties
+ this.$tabIndexed = null;
+ this.tabIndex = null;
+
+ // Events
+ this.connect( this, { disable: 'onDisable' } );
+
+ // Initialization
+ this.setTabIndex( config.tabIndex );
+ this.setTabIndexedElement( config.$tabIndexed || this.$element );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.TabIndexedElement );
+
+/* Methods */
+
+/**
+ * Set the element that should use the tabindex functionality.
+ *
+ * This method is used to retarget a tabindex mixin so that its functionality applies
+ * to the specified element. If an element is currently using the functionality, the mixin’s
+ * effect on that element is removed before the new element is set up.
+ *
+ * @param {jQuery} $tabIndexed Element that should use the tabindex functionality
+ * @chainable
+ */
+OO.ui.TabIndexedElement.prototype.setTabIndexedElement = function ( $tabIndexed ) {
+ var tabIndex = this.tabIndex;
+ // Remove attributes from old $tabIndexed
+ this.setTabIndex( null );
+ // Force update of new $tabIndexed
+ this.$tabIndexed = $tabIndexed;
+ this.tabIndex = tabIndex;
+ return this.updateTabIndex();
+};
+
+/**
+ * Set the value of the tabindex.
+ *
+ * @param {number|null} tabIndex Tabindex value, or `null` for no tabindex
+ * @chainable
+ */
+OO.ui.TabIndexedElement.prototype.setTabIndex = function ( tabIndex ) {
+ tabIndex = typeof tabIndex === 'number' ? tabIndex : null;
+
+ if ( this.tabIndex !== tabIndex ) {
+ this.tabIndex = tabIndex;
+ this.updateTabIndex();
+ }
+
+ return this;
+};
+
+/**
+ * Update the `tabindex` attribute, in case of changes to tab index or
+ * disabled state.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.TabIndexedElement.prototype.updateTabIndex = function () {
+ if ( this.$tabIndexed ) {
+ if ( this.tabIndex !== null ) {
+ // Do not index over disabled elements
+ this.$tabIndexed.attr( {
+ tabindex: this.isDisabled() ? -1 : this.tabIndex,
+ // ChromeVox and NVDA do not seem to inherit this from parent elements
+ 'aria-disabled': this.isDisabled().toString()
+ } );
+ } else {
+ this.$tabIndexed.removeAttr( 'tabindex aria-disabled' );
+ }
+ }
+ return this;
+};
+
+/**
+ * Handle disable events.
+ *
+ * @private
+ * @param {boolean} disabled Element is disabled
+ */
+OO.ui.TabIndexedElement.prototype.onDisable = function () {
+ this.updateTabIndex();
+};
+
+/**
+ * Get the value of the tabindex.
+ *
+ * @return {number|null} Tabindex value
+ */
+OO.ui.TabIndexedElement.prototype.getTabIndex = function () {
+ return this.tabIndex;
+};
diff --git a/vendor/oojs/oojs-ui/src/elements/TitledElement.js b/vendor/oojs/oojs-ui/src/elements/TitledElement.js
new file mode 100644
index 00000000..905ec019
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/elements/TitledElement.js
@@ -0,0 +1,106 @@
+/**
+ * TitledElement is mixed into other classes to provide a `title` attribute.
+ * Titles are rendered by the browser and are made visible when the user moves
+ * the mouse over the element. Titles are not visible on touch devices.
+ *
+ * @example
+ * // TitledElement provides a 'title' attribute to the
+ * // ButtonWidget class
+ * var button = new OO.ui.ButtonWidget( {
+ * label: 'Button with Title',
+ * title: 'I am a button'
+ * } );
+ * $( 'body' ).append( button.$element );
+ *
+ * @abstract
+ * @class
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$titled] The element to which the `title` attribute is applied.
+ * If this config is omitted, the title functionality is applied to $element, the
+ * element created by the class.
+ * @cfg {string|Function} [title] The title text or a function that returns text. If
+ * this config is omitted, the value of the {@link #static-title static title} property is used.
+ */
+OO.ui.TitledElement = function OoUiTitledElement( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties
+ this.$titled = null;
+ this.title = null;
+
+ // Initialization
+ this.setTitle( config.title || this.constructor.static.title );
+ this.setTitledElement( config.$titled || this.$element );
+};
+
+/* Setup */
+
+OO.initClass( OO.ui.TitledElement );
+
+/* Static Properties */
+
+/**
+ * The title text, a function that returns text, or `null` for no title. The value of the static property
+ * is overridden if the #title config option is used.
+ *
+ * @static
+ * @inheritable
+ * @property {string|Function|null}
+ */
+OO.ui.TitledElement.static.title = null;
+
+/* Methods */
+
+/**
+ * Set the titled element.
+ *
+ * This method is used to retarget a titledElement mixin so that its functionality applies to the specified element.
+ * If an element is already set, the mixin’s effect on that element is removed before the new element is set up.
+ *
+ * @param {jQuery} $titled Element that should use the 'titled' functionality
+ */
+OO.ui.TitledElement.prototype.setTitledElement = function ( $titled ) {
+ if ( this.$titled ) {
+ this.$titled.removeAttr( 'title' );
+ }
+
+ this.$titled = $titled;
+ if ( this.title ) {
+ this.$titled.attr( 'title', this.title );
+ }
+};
+
+/**
+ * Set title.
+ *
+ * @param {string|Function|null} title Title text, a function that returns text, or `null` for no title
+ * @chainable
+ */
+OO.ui.TitledElement.prototype.setTitle = function ( title ) {
+ title = typeof title === 'string' ? OO.ui.resolveMsg( title ) : null;
+
+ if ( this.title !== title ) {
+ if ( this.$titled ) {
+ if ( title !== null ) {
+ this.$titled.attr( 'title', title );
+ } else {
+ this.$titled.removeAttr( 'title' );
+ }
+ }
+ this.title = title;
+ }
+
+ return this;
+};
+
+/**
+ * Get title.
+ *
+ * @return {string} Title string
+ */
+OO.ui.TitledElement.prototype.getTitle = function () {
+ return this.title;
+};
diff --git a/vendor/oojs/oojs-ui/src/intro.js.txt b/vendor/oojs/oojs-ui/src/intro.js.txt
new file mode 100644
index 00000000..31f545ca
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/intro.js.txt
@@ -0,0 +1,3 @@
+( function ( OO ) {
+
+'use strict';
diff --git a/vendor/oojs/oojs-ui/src/layouts/ActionFieldLayout.js b/vendor/oojs/oojs-ui/src/layouts/ActionFieldLayout.js
new file mode 100644
index 00000000..59640ed9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/ActionFieldLayout.js
@@ -0,0 +1,81 @@
+/**
+ * ActionFieldLayouts are used with OO.ui.FieldsetLayout. The layout consists of a field-widget, a button,
+ * and an optional label and/or help text. The field-widget (e.g., a {@link OO.ui.TextInputWidget TextInputWidget}),
+ * is required and is specified before any optional configuration settings.
+ *
+ * Labels can be aligned in one of four ways:
+ *
+ * - **left**: The label is placed before the field-widget and aligned with the left margin.
+ * A left-alignment is used for forms with many fields.
+ * - **right**: The label is placed before the field-widget and aligned to the right margin.
+ * A right-alignment is used for long but familiar forms which users tab through,
+ * verifying the current field with a quick glance at the label.
+ * - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms
+ * that users fill out from top to bottom.
+ * - **inline**: The label is placed after the field-widget and aligned to the left.
+ * An inline-alignment is best used with checkboxes or radio buttons.
+ *
+ * Help text is accessed via a help icon that appears in the upper right corner of the rendered field layout when help
+ * text is specified.
+ *
+ * @example
+ * // Example of an ActionFieldLayout
+ * var actionFieldLayout = new OO.ui.ActionFieldLayout(
+ * new OO.ui.TextInputWidget( {
+ * placeholder: 'Field widget'
+ * } ),
+ * new OO.ui.ButtonWidget( {
+ * label: 'Button'
+ * } ),
+ * {
+ * label: 'An ActionFieldLayout. This label is aligned top',
+ * align: 'top',
+ * help: 'This is help text'
+ * }
+ * );
+ *
+ * $( 'body' ).append( actionFieldLayout.$element );
+ *
+ *
+ * @class
+ * @extends OO.ui.FieldLayout
+ *
+ * @constructor
+ * @param {OO.ui.Widget} fieldWidget Field widget
+ * @param {OO.ui.ButtonWidget} buttonWidget Button widget
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [align='left'] Alignment of the label: 'left', 'right', 'top' or 'inline'
+ * @cfg {string} [help] Help text. When help text is specified, a help icon will appear in the
+ * upper-right corner of the rendered field.
+ */
+OO.ui.ActionFieldLayout = function OoUiActionFieldLayout( fieldWidget, buttonWidget, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
+ config = fieldWidget;
+ fieldWidget = config.fieldWidget;
+ buttonWidget = config.buttonWidget;
+ }
+
+ // Configuration initialization
+ config = $.extend( { align: 'left' }, config );
+
+ // Parent constructor
+ OO.ui.ActionFieldLayout.super.call( this, fieldWidget, config );
+
+ // Properties
+ this.fieldWidget = fieldWidget;
+ this.buttonWidget = buttonWidget;
+ this.$button = $( '<div>' )
+ .addClass( 'oo-ui-actionFieldLayout-button' )
+ .append( this.buttonWidget.$element );
+ this.$input = $( '<div>' )
+ .addClass( 'oo-ui-actionFieldLayout-input' )
+ .append( this.fieldWidget.$element );
+ this.$field
+ .addClass( 'oo-ui-actionFieldLayout' )
+ .append( this.$input, this.$button );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ActionFieldLayout, OO.ui.FieldLayout );
diff --git a/vendor/oojs/oojs-ui/src/layouts/BookletLayout.js b/vendor/oojs/oojs-ui/src/layouts/BookletLayout.js
new file mode 100644
index 00000000..eebf57d6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/BookletLayout.js
@@ -0,0 +1,542 @@
+/**
+ * BookletLayouts contain {@link OO.ui.PageLayout page layouts} as well as
+ * an {@link OO.ui.OutlineSelectWidget outline} that allows users to easily navigate
+ * through the pages and select which one to display. By default, only one page is
+ * displayed at a time and the outline is hidden. When a user navigates to a new page,
+ * the booklet layout automatically focuses on the first focusable element, unless the
+ * default setting is changed. Optionally, booklets can be configured to show
+ * {@link OO.ui.OutlineControlsWidget controls} for adding, moving, and removing items.
+ *
+ * @example
+ * // Example of a BookletLayout that contains two PageLayouts.
+ *
+ * function PageOneLayout( name, config ) {
+ * PageOneLayout.super.call( this, name, config );
+ * this.$element.append( '<p>First page</p><p>(This booklet has an outline, displayed on the left)</p>' );
+ * }
+ * OO.inheritClass( PageOneLayout, OO.ui.PageLayout );
+ * PageOneLayout.prototype.setupOutlineItem = function () {
+ * this.outlineItem.setLabel( 'Page One' );
+ * };
+ *
+ * function PageTwoLayout( name, config ) {
+ * PageTwoLayout.super.call( this, name, config );
+ * this.$element.append( '<p>Second page</p>' );
+ * }
+ * OO.inheritClass( PageTwoLayout, OO.ui.PageLayout );
+ * PageTwoLayout.prototype.setupOutlineItem = function () {
+ * this.outlineItem.setLabel( 'Page Two' );
+ * };
+ *
+ * var page1 = new PageOneLayout( 'one' ),
+ * page2 = new PageTwoLayout( 'two' );
+ *
+ * var booklet = new OO.ui.BookletLayout( {
+ * outlined: true
+ * } );
+ *
+ * booklet.addPages ( [ page1, page2 ] );
+ * $( 'body' ).append( booklet.$element );
+ *
+ * @class
+ * @extends OO.ui.MenuLayout
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [continuous=false] Show all pages, one after another
+ * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new page is displayed.
+ * @cfg {boolean} [outlined=false] Show the outline. The outline is used to navigate through the pages of the booklet.
+ * @cfg {boolean} [editable=false] Show controls for adding, removing and reordering pages
+ */
+OO.ui.BookletLayout = function OoUiBookletLayout( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.BookletLayout.super.call( this, config );
+
+ // Properties
+ this.currentPageName = null;
+ this.pages = {};
+ this.ignoreFocus = false;
+ this.stackLayout = new OO.ui.StackLayout( { continuous: !!config.continuous } );
+ this.$content.append( this.stackLayout.$element );
+ this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;
+ this.outlineVisible = false;
+ this.outlined = !!config.outlined;
+ if ( this.outlined ) {
+ this.editable = !!config.editable;
+ this.outlineControlsWidget = null;
+ this.outlineSelectWidget = new OO.ui.OutlineSelectWidget();
+ this.outlinePanel = new OO.ui.PanelLayout( { scrollable: true } );
+ this.$menu.append( this.outlinePanel.$element );
+ this.outlineVisible = true;
+ if ( this.editable ) {
+ this.outlineControlsWidget = new OO.ui.OutlineControlsWidget(
+ this.outlineSelectWidget
+ );
+ }
+ }
+ this.toggleMenu( this.outlined );
+
+ // Events
+ this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
+ if ( this.outlined ) {
+ this.outlineSelectWidget.connect( this, { select: 'onOutlineSelectWidgetSelect' } );
+ }
+ if ( this.autoFocus ) {
+ // Event 'focus' does not bubble, but 'focusin' does
+ this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
+ }
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-bookletLayout' );
+ this.stackLayout.$element.addClass( 'oo-ui-bookletLayout-stackLayout' );
+ if ( this.outlined ) {
+ this.outlinePanel.$element
+ .addClass( 'oo-ui-bookletLayout-outlinePanel' )
+ .append( this.outlineSelectWidget.$element );
+ if ( this.editable ) {
+ this.outlinePanel.$element
+ .addClass( 'oo-ui-bookletLayout-outlinePanel-editable' )
+ .append( this.outlineControlsWidget.$element );
+ }
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.BookletLayout, OO.ui.MenuLayout );
+
+/* Events */
+
+/**
+ * A 'set' event is emitted when a page is {@link #setPage set} to be displayed by the booklet layout.
+ * @event set
+ * @param {OO.ui.PageLayout} page Current page
+ */
+
+/**
+ * An 'add' event is emitted when pages are {@link #addPages added} to the booklet layout.
+ *
+ * @event add
+ * @param {OO.ui.PageLayout[]} page Added pages
+ * @param {number} index Index pages were added at
+ */
+
+/**
+ * A 'remove' event is emitted when pages are {@link #clearPages cleared} or
+ * {@link #removePages removed} from the booklet.
+ *
+ * @event remove
+ * @param {OO.ui.PageLayout[]} pages Removed pages
+ */
+
+/* Methods */
+
+/**
+ * Handle stack layout focus.
+ *
+ * @private
+ * @param {jQuery.Event} e Focusin event
+ */
+OO.ui.BookletLayout.prototype.onStackLayoutFocus = function ( e ) {
+ var name, $target;
+
+ // Find the page that an element was focused within
+ $target = $( e.target ).closest( '.oo-ui-pageLayout' );
+ for ( name in this.pages ) {
+ // Check for page match, exclude current page to find only page changes
+ if ( this.pages[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentPageName ) {
+ this.setPage( name );
+ break;
+ }
+ }
+};
+
+/**
+ * Handle stack layout set events.
+ *
+ * @private
+ * @param {OO.ui.PanelLayout|null} page The page panel that is now the current panel
+ */
+OO.ui.BookletLayout.prototype.onStackLayoutSet = function ( page ) {
+ var layout = this;
+ if ( page ) {
+ page.scrollElementIntoView( { complete: function () {
+ if ( layout.autoFocus ) {
+ layout.focus();
+ }
+ } } );
+ }
+};
+
+/**
+ * Focus the first input in the current page.
+ *
+ * If no page is selected, the first selectable page will be selected.
+ * If the focus is already in an element on the current page, nothing will happen.
+ * @param {number} [itemIndex] A specific item to focus on
+ */
+OO.ui.BookletLayout.prototype.focus = function ( itemIndex ) {
+ var $input, page,
+ items = this.stackLayout.getItems();
+
+ if ( itemIndex !== undefined && items[ itemIndex ] ) {
+ page = items[ itemIndex ];
+ } else {
+ page = this.stackLayout.getCurrentItem();
+ }
+
+ if ( !page && this.outlined ) {
+ this.selectFirstSelectablePage();
+ page = this.stackLayout.getCurrentItem();
+ }
+ if ( !page ) {
+ return;
+ }
+ // Only change the focus if is not already in the current page
+ if ( !page.$element.find( ':focus' ).length ) {
+ $input = page.$element.find( ':input:first' );
+ if ( $input.length ) {
+ $input[ 0 ].focus();
+ }
+ }
+};
+
+/**
+ * Find the first focusable input in the booklet layout and focus
+ * on it.
+ */
+OO.ui.BookletLayout.prototype.focusFirstFocusable = function () {
+ var i, len,
+ found = false,
+ items = this.stackLayout.getItems(),
+ checkAndFocus = function () {
+ if ( OO.ui.isFocusableElement( $( this ) ) ) {
+ $( this ).focus();
+ found = true;
+ return false;
+ }
+ };
+
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ if ( found ) {
+ break;
+ }
+ // Find all potentially focusable elements in the item
+ // and check if they are focusable
+ items[i].$element
+ .find( 'input, select, textarea, button, object' )
+ /* jshint loopfunc:true */
+ .each( checkAndFocus );
+ }
+};
+
+/**
+ * Handle outline widget select events.
+ *
+ * @private
+ * @param {OO.ui.OptionWidget|null} item Selected item
+ */
+OO.ui.BookletLayout.prototype.onOutlineSelectWidgetSelect = function ( item ) {
+ if ( item ) {
+ this.setPage( item.getData() );
+ }
+};
+
+/**
+ * Check if booklet has an outline.
+ *
+ * @return {boolean} Booklet has an outline
+ */
+OO.ui.BookletLayout.prototype.isOutlined = function () {
+ return this.outlined;
+};
+
+/**
+ * Check if booklet has editing controls.
+ *
+ * @return {boolean} Booklet is editable
+ */
+OO.ui.BookletLayout.prototype.isEditable = function () {
+ return this.editable;
+};
+
+/**
+ * Check if booklet has a visible outline.
+ *
+ * @return {boolean} Outline is visible
+ */
+OO.ui.BookletLayout.prototype.isOutlineVisible = function () {
+ return this.outlined && this.outlineVisible;
+};
+
+/**
+ * Hide or show the outline.
+ *
+ * @param {boolean} [show] Show outline, omit to invert current state
+ * @chainable
+ */
+OO.ui.BookletLayout.prototype.toggleOutline = function ( show ) {
+ if ( this.outlined ) {
+ show = show === undefined ? !this.outlineVisible : !!show;
+ this.outlineVisible = show;
+ this.toggleMenu( show );
+ }
+
+ return this;
+};
+
+/**
+ * Get the page closest to the specified page.
+ *
+ * @param {OO.ui.PageLayout} page Page to use as a reference point
+ * @return {OO.ui.PageLayout|null} Page closest to the specified page
+ */
+OO.ui.BookletLayout.prototype.getClosestPage = function ( page ) {
+ var next, prev, level,
+ pages = this.stackLayout.getItems(),
+ index = $.inArray( page, pages );
+
+ if ( index !== -1 ) {
+ next = pages[ index + 1 ];
+ prev = pages[ index - 1 ];
+ // Prefer adjacent pages at the same level
+ if ( this.outlined ) {
+ level = this.outlineSelectWidget.getItemFromData( page.getName() ).getLevel();
+ if (
+ prev &&
+ level === this.outlineSelectWidget.getItemFromData( prev.getName() ).getLevel()
+ ) {
+ return prev;
+ }
+ if (
+ next &&
+ level === this.outlineSelectWidget.getItemFromData( next.getName() ).getLevel()
+ ) {
+ return next;
+ }
+ }
+ }
+ return prev || next || null;
+};
+
+/**
+ * Get the outline widget.
+ *
+ * If the booklet is not outlined, the method will return `null`.
+ *
+ * @return {OO.ui.OutlineSelectWidget|null} Outline widget, or null if the booklet is not outlined
+ */
+OO.ui.BookletLayout.prototype.getOutline = function () {
+ return this.outlineSelectWidget;
+};
+
+/**
+ * Get the outline controls widget.
+ *
+ * If the outline is not editable, the method will return `null`.
+ *
+ * @return {OO.ui.OutlineControlsWidget|null} The outline controls widget.
+ */
+OO.ui.BookletLayout.prototype.getOutlineControls = function () {
+ return this.outlineControlsWidget;
+};
+
+/**
+ * Get a page by its symbolic name.
+ *
+ * @param {string} name Symbolic name of page
+ * @return {OO.ui.PageLayout|undefined} Page, if found
+ */
+OO.ui.BookletLayout.prototype.getPage = function ( name ) {
+ return this.pages[ name ];
+};
+
+/**
+ * Get the current page.
+ *
+ * @return {OO.ui.PageLayout|undefined} Current page, if found
+ */
+OO.ui.BookletLayout.prototype.getCurrentPage = function () {
+ var name = this.getCurrentPageName();
+ return name ? this.getPage( name ) : undefined;
+};
+
+/**
+ * Get the symbolic name of the current page.
+ *
+ * @return {string|null} Symbolic name of the current page
+ */
+OO.ui.BookletLayout.prototype.getCurrentPageName = function () {
+ return this.currentPageName;
+};
+
+/**
+ * Add pages to the booklet layout
+ *
+ * When pages are added with the same names as existing pages, the existing pages will be
+ * automatically removed before the new pages are added.
+ *
+ * @param {OO.ui.PageLayout[]} pages Pages to add
+ * @param {number} index Index of the insertion point
+ * @fires add
+ * @chainable
+ */
+OO.ui.BookletLayout.prototype.addPages = function ( pages, index ) {
+ var i, len, name, page, item, currentIndex,
+ stackLayoutPages = this.stackLayout.getItems(),
+ remove = [],
+ items = [];
+
+ // Remove pages with same names
+ for ( i = 0, len = pages.length; i < len; i++ ) {
+ page = pages[ i ];
+ name = page.getName();
+
+ if ( Object.prototype.hasOwnProperty.call( this.pages, name ) ) {
+ // Correct the insertion index
+ currentIndex = $.inArray( this.pages[ name ], stackLayoutPages );
+ if ( currentIndex !== -1 && currentIndex + 1 < index ) {
+ index--;
+ }
+ remove.push( this.pages[ name ] );
+ }
+ }
+ if ( remove.length ) {
+ this.removePages( remove );
+ }
+
+ // Add new pages
+ for ( i = 0, len = pages.length; i < len; i++ ) {
+ page = pages[ i ];
+ name = page.getName();
+ this.pages[ page.getName() ] = page;
+ if ( this.outlined ) {
+ item = new OO.ui.OutlineOptionWidget( { data: name } );
+ page.setOutlineItem( item );
+ items.push( item );
+ }
+ }
+
+ if ( this.outlined && items.length ) {
+ this.outlineSelectWidget.addItems( items, index );
+ this.selectFirstSelectablePage();
+ }
+ this.stackLayout.addItems( pages, index );
+ this.emit( 'add', pages, index );
+
+ return this;
+};
+
+/**
+ * Remove the specified pages from the booklet layout.
+ *
+ * To remove all pages from the booklet, you may wish to use the #clearPages method instead.
+ *
+ * @param {OO.ui.PageLayout[]} pages An array of pages to remove
+ * @fires remove
+ * @chainable
+ */
+OO.ui.BookletLayout.prototype.removePages = function ( pages ) {
+ var i, len, name, page,
+ items = [];
+
+ for ( i = 0, len = pages.length; i < len; i++ ) {
+ page = pages[ i ];
+ name = page.getName();
+ delete this.pages[ name ];
+ if ( this.outlined ) {
+ items.push( this.outlineSelectWidget.getItemFromData( name ) );
+ page.setOutlineItem( null );
+ }
+ }
+ if ( this.outlined && items.length ) {
+ this.outlineSelectWidget.removeItems( items );
+ this.selectFirstSelectablePage();
+ }
+ this.stackLayout.removeItems( pages );
+ this.emit( 'remove', pages );
+
+ return this;
+};
+
+/**
+ * Clear all pages from the booklet layout.
+ *
+ * To remove only a subset of pages from the booklet, use the #removePages method.
+ *
+ * @fires remove
+ * @chainable
+ */
+OO.ui.BookletLayout.prototype.clearPages = function () {
+ var i, len,
+ pages = this.stackLayout.getItems();
+
+ this.pages = {};
+ this.currentPageName = null;
+ if ( this.outlined ) {
+ this.outlineSelectWidget.clearItems();
+ for ( i = 0, len = pages.length; i < len; i++ ) {
+ pages[ i ].setOutlineItem( null );
+ }
+ }
+ this.stackLayout.clearItems();
+
+ this.emit( 'remove', pages );
+
+ return this;
+};
+
+/**
+ * Set the current page by symbolic name.
+ *
+ * @fires set
+ * @param {string} name Symbolic name of page
+ */
+OO.ui.BookletLayout.prototype.setPage = function ( name ) {
+ var selectedItem,
+ $focused,
+ page = this.pages[ name ];
+
+ if ( name !== this.currentPageName ) {
+ if ( this.outlined ) {
+ selectedItem = this.outlineSelectWidget.getSelectedItem();
+ if ( selectedItem && selectedItem.getData() !== name ) {
+ this.outlineSelectWidget.selectItemByData( name );
+ }
+ }
+ if ( page ) {
+ if ( this.currentPageName && this.pages[ this.currentPageName ] ) {
+ this.pages[ this.currentPageName ].setActive( false );
+ // Blur anything focused if the next page doesn't have anything focusable - this
+ // is not needed if the next page has something focusable because once it is focused
+ // this blur happens automatically
+ if ( this.autoFocus && !page.$element.find( ':input' ).length ) {
+ $focused = this.pages[ this.currentPageName ].$element.find( ':focus' );
+ if ( $focused.length ) {
+ $focused[ 0 ].blur();
+ }
+ }
+ }
+ this.currentPageName = name;
+ this.stackLayout.setItem( page );
+ page.setActive( true );
+ this.emit( 'set', page );
+ }
+ }
+};
+
+/**
+ * Select the first selectable page.
+ *
+ * @chainable
+ */
+OO.ui.BookletLayout.prototype.selectFirstSelectablePage = function () {
+ if ( !this.outlineSelectWidget.getSelectedItem() ) {
+ this.outlineSelectWidget.selectItem( this.outlineSelectWidget.getFirstSelectableItem() );
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/layouts/CardLayout.js b/vendor/oojs/oojs-ui/src/layouts/CardLayout.js
new file mode 100644
index 00000000..353aedc9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/CardLayout.js
@@ -0,0 +1,138 @@
+/**
+ * CardLayouts are used within {@link OO.ui.IndexLayout index layouts} to create cards that users can select and display
+ * from the index's optional {@link OO.ui.TabSelectWidget tab} navigation. Cards are usually not instantiated directly,
+ * rather extended to include the required content and functionality.
+ *
+ * Each card must have a unique symbolic name, which is passed to the constructor. In addition, the card's tab
+ * item is customized (with a label) using the #setupTabItem method. See
+ * {@link OO.ui.IndexLayout IndexLayout} for an example.
+ *
+ * @class
+ * @extends OO.ui.PanelLayout
+ *
+ * @constructor
+ * @param {string} name Unique symbolic name of card
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.CardLayout = function OoUiCardLayout( name, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( name ) && config === undefined ) {
+ config = name;
+ name = config.name;
+ }
+
+ // Configuration initialization
+ config = $.extend( { scrollable: true }, config );
+
+ // Parent constructor
+ OO.ui.CardLayout.super.call( this, config );
+
+ // Properties
+ this.name = name;
+ this.tabItem = null;
+ this.active = false;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-cardLayout' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.CardLayout, OO.ui.PanelLayout );
+
+/* Events */
+
+/**
+ * An 'active' event is emitted when the card becomes active. Cards become active when they are
+ * shown in a index layout that is configured to display only one card at a time.
+ *
+ * @event active
+ * @param {boolean} active Card is active
+ */
+
+/* Methods */
+
+/**
+ * Get the symbolic name of the card.
+ *
+ * @return {string} Symbolic name of card
+ */
+OO.ui.CardLayout.prototype.getName = function () {
+ return this.name;
+};
+
+/**
+ * Check if card is active.
+ *
+ * Cards become active when they are shown in a {@link OO.ui.IndexLayout index layout} that is configured to display
+ * only one card at a time. Additional CSS is applied to the card's tab item to reflect the active state.
+ *
+ * @return {boolean} Card is active
+ */
+OO.ui.CardLayout.prototype.isActive = function () {
+ return this.active;
+};
+
+/**
+ * Get tab item.
+ *
+ * The tab item allows users to access the card from the index's tab
+ * navigation. The tab item itself can be customized (with a label, level, etc.) using the #setupTabItem method.
+ *
+ * @return {OO.ui.TabOptionWidget|null} Tab option widget
+ */
+OO.ui.CardLayout.prototype.getTabItem = function () {
+ return this.tabItem;
+};
+
+/**
+ * Set or unset the tab item.
+ *
+ * Specify a {@link OO.ui.TabOptionWidget tab option} to set it,
+ * or `null` to clear the tab item. To customize the tab item itself (e.g., to set a label or tab
+ * level), use #setupTabItem instead of this method.
+ *
+ * @param {OO.ui.TabOptionWidget|null} tabItem Tab option widget, null to clear
+ * @chainable
+ */
+OO.ui.CardLayout.prototype.setTabItem = function ( tabItem ) {
+ this.tabItem = tabItem || null;
+ if ( tabItem ) {
+ this.setupTabItem();
+ }
+ return this;
+};
+
+/**
+ * Set up the tab item.
+ *
+ * Use this method to customize the tab item (e.g., to add a label or tab level). To set or unset
+ * the tab item itself (with a {@link OO.ui.TabOptionWidget tab option} or `null`), use
+ * the #setTabItem method instead.
+ *
+ * @param {OO.ui.TabOptionWidget} tabItem Tab option widget to set up
+ * @chainable
+ */
+OO.ui.CardLayout.prototype.setupTabItem = function () {
+ return this;
+};
+
+/**
+ * Set the card to its 'active' state.
+ *
+ * Cards become active when they are shown in a index layout that is configured to display only one card at a time. Additional
+ * CSS is applied to the tab item to reflect the card's active state. Outside of the index
+ * context, setting the active state on a card does nothing.
+ *
+ * @param {boolean} value Card is active
+ * @fires active
+ */
+OO.ui.CardLayout.prototype.setActive = function ( active ) {
+ active = !!active;
+
+ if ( active !== this.active ) {
+ this.active = active;
+ this.$element.toggleClass( 'oo-ui-cardLayout-active', this.active );
+ this.emit( 'active', this.active );
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/layouts/FieldLayout.js b/vendor/oojs/oojs-ui/src/layouts/FieldLayout.js
new file mode 100644
index 00000000..86385741
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/FieldLayout.js
@@ -0,0 +1,160 @@
+/**
+ * FieldLayouts are used with OO.ui.FieldsetLayout. Each FieldLayout requires a field-widget,
+ * which is a widget that is specified by reference before any optional configuration settings.
+ *
+ * Field layouts can be configured with help text and/or labels. Labels are aligned in one of four ways:
+ *
+ * - **left**: The label is placed before the field-widget and aligned with the left margin.
+ * A left-alignment is used for forms with many fields.
+ * - **right**: The label is placed before the field-widget and aligned to the right margin.
+ * A right-alignment is used for long but familiar forms which users tab through,
+ * verifying the current field with a quick glance at the label.
+ * - **top**: The label is placed above the field-widget. A top-alignment is used for brief forms
+ * that users fill out from top to bottom.
+ * - **inline**: The label is placed after the field-widget and aligned to the left.
+ * An inline-alignment is best used with checkboxes or radio buttons.
+ *
+ * Help text is accessed via a help icon that appears in the upper right corner of the rendered field layout.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for examples and more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Fields_and_Fieldsets
+ * @class
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.LabelElement
+ *
+ * @constructor
+ * @param {OO.ui.Widget} fieldWidget Field widget
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [align='left'] Alignment of the label: 'left', 'right', 'top' or 'inline'
+ * @cfg {string} [help] Help text. When help text is specified, a help icon will appear
+ * in the upper-right corner of the rendered field.
+ */
+OO.ui.FieldLayout = function OoUiFieldLayout( fieldWidget, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( fieldWidget ) && config === undefined ) {
+ config = fieldWidget;
+ fieldWidget = config.fieldWidget;
+ }
+
+ var hasInputWidget = fieldWidget instanceof OO.ui.InputWidget;
+
+ // Configuration initialization
+ config = $.extend( { align: 'left' }, config );
+
+ // Parent constructor
+ OO.ui.FieldLayout.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.LabelElement.call( this, config );
+
+ // Properties
+ this.fieldWidget = fieldWidget;
+ this.$field = $( '<div>' );
+ this.$body = $( '<' + ( hasInputWidget ? 'label' : 'div' ) + '>' );
+ this.align = null;
+ if ( config.help ) {
+ this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
+ classes: [ 'oo-ui-fieldLayout-help' ],
+ framed: false,
+ icon: 'info'
+ } );
+
+ this.popupButtonWidget.getPopup().$body.append(
+ $( '<div>' )
+ .text( config.help )
+ .addClass( 'oo-ui-fieldLayout-help-content' )
+ );
+ this.$help = this.popupButtonWidget.$element;
+ } else {
+ this.$help = $( [] );
+ }
+
+ // Events
+ if ( hasInputWidget ) {
+ this.$label.on( 'click', this.onLabelClick.bind( this ) );
+ }
+ this.fieldWidget.connect( this, { disable: 'onFieldDisable' } );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-fieldLayout' )
+ .append( this.$help, this.$body );
+ this.$body.addClass( 'oo-ui-fieldLayout-body' );
+ this.$field
+ .addClass( 'oo-ui-fieldLayout-field' )
+ .toggleClass( 'oo-ui-fieldLayout-disable', this.fieldWidget.isDisabled() )
+ .append( this.fieldWidget.$element );
+
+ this.setAlignment( config.align );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.FieldLayout, OO.ui.Layout );
+OO.mixinClass( OO.ui.FieldLayout, OO.ui.LabelElement );
+
+/* Methods */
+
+/**
+ * Handle field disable events.
+ *
+ * @private
+ * @param {boolean} value Field is disabled
+ */
+OO.ui.FieldLayout.prototype.onFieldDisable = function ( value ) {
+ this.$element.toggleClass( 'oo-ui-fieldLayout-disabled', value );
+};
+
+/**
+ * Handle label mouse click events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse click event
+ */
+OO.ui.FieldLayout.prototype.onLabelClick = function () {
+ this.fieldWidget.simulateLabelClick();
+ return false;
+};
+
+/**
+ * Get the widget contained by the field.
+ *
+ * @return {OO.ui.Widget} Field widget
+ */
+OO.ui.FieldLayout.prototype.getField = function () {
+ return this.fieldWidget;
+};
+
+/**
+ * Set the field alignment mode.
+ *
+ * @private
+ * @param {string} value Alignment mode, either 'left', 'right', 'top' or 'inline'
+ * @chainable
+ */
+OO.ui.FieldLayout.prototype.setAlignment = function ( value ) {
+ if ( value !== this.align ) {
+ // Default to 'left'
+ if ( [ 'left', 'right', 'top', 'inline' ].indexOf( value ) === -1 ) {
+ value = 'left';
+ }
+ // Reorder elements
+ if ( value === 'inline' ) {
+ this.$body.append( this.$field, this.$label );
+ } else {
+ this.$body.append( this.$label, this.$field );
+ }
+ // Set classes. The following classes can be used here:
+ // * oo-ui-fieldLayout-align-left
+ // * oo-ui-fieldLayout-align-right
+ // * oo-ui-fieldLayout-align-top
+ // * oo-ui-fieldLayout-align-inline
+ if ( this.align ) {
+ this.$element.removeClass( 'oo-ui-fieldLayout-align-' + this.align );
+ }
+ this.$element.addClass( 'oo-ui-fieldLayout-align-' + value );
+ this.align = value;
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/layouts/FieldsetLayout.js b/vendor/oojs/oojs-ui/src/layouts/FieldsetLayout.js
new file mode 100644
index 00000000..1a21e8e1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/FieldsetLayout.js
@@ -0,0 +1,86 @@
+/**
+ * FieldsetLayouts are composed of one or more {@link OO.ui.FieldLayout FieldLayouts},
+ * which each contain an individual widget and, optionally, a label. Each Fieldset can be
+ * configured with a label as well. For more information and examples,
+ * please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * @example
+ * // Example of a fieldset layout
+ * var input1 = new OO.ui.TextInputWidget( {
+ * placeholder: 'A text input field'
+ * } );
+ *
+ * var input2 = new OO.ui.TextInputWidget( {
+ * placeholder: 'A text input field'
+ * } );
+ *
+ * var fieldset = new OO.ui.FieldsetLayout( {
+ * label: 'Example of a fieldset layout'
+ * } );
+ *
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( input1, {
+ * label: 'Field One'
+ * } ),
+ * new OO.ui.FieldLayout( input2, {
+ * label: 'Field Two'
+ * } )
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Fields_and_Fieldsets
+ *
+ * @class
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.FieldLayout[]} [items] An array of fields to add to the fieldset. See OO.ui.FieldLayout for more information about fields.
+ */
+OO.ui.FieldsetLayout = function OoUiFieldsetLayout( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.FieldsetLayout.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.GroupElement.call( this, config );
+
+ if ( config.help ) {
+ this.popupButtonWidget = new OO.ui.PopupButtonWidget( {
+ classes: [ 'oo-ui-fieldsetLayout-help' ],
+ framed: false,
+ icon: 'info'
+ } );
+
+ this.popupButtonWidget.getPopup().$body.append(
+ $( '<div>' )
+ .text( config.help )
+ .addClass( 'oo-ui-fieldsetLayout-help-content' )
+ );
+ this.$help = this.popupButtonWidget.$element;
+ } else {
+ this.$help = $( [] );
+ }
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-fieldsetLayout' )
+ .prepend( this.$help, this.$icon, this.$label, this.$group );
+ if ( Array.isArray( config.items ) ) {
+ this.addItems( config.items );
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.FieldsetLayout, OO.ui.Layout );
+OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.IconElement );
+OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.FieldsetLayout, OO.ui.GroupElement );
diff --git a/vendor/oojs/oojs-ui/src/layouts/FormLayout.js b/vendor/oojs/oojs-ui/src/layouts/FormLayout.js
new file mode 100644
index 00000000..a131c169
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/FormLayout.js
@@ -0,0 +1,119 @@
+/**
+ * FormLayouts are used to wrap {@link OO.ui.FieldsetLayout FieldsetLayouts} when you intend to use browser-based
+ * form submission for the fields instead of handling them in JavaScript. Form layouts can be configured with an
+ * HTML form action, an encoding type, and a method using the #action, #enctype, and #method configs, respectively.
+ * See the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
+ *
+ * Only widgets from the {@link OO.ui.InputWidget InputWidget} family support form submission. It
+ * includes standard form elements like {@link OO.ui.CheckboxInputWidget checkboxes}, {@link
+ * OO.ui.RadioInputWidget radio buttons} and {@link OO.ui.TextInputWidget text fields}, as well as
+ * some fancier controls. Some controls have both regular and InputWidget variants, for example
+ * OO.ui.DropdownWidget and OO.ui.DropdownInputWidget – only the latter support form submission and
+ * often have simplified APIs to match the capabilities of HTML forms.
+ * See the [OOjs UI Inputs documentation on MediaWiki] [2] for more information about InputWidgets.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Layouts/Forms
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @example
+ * // Example of a form layout that wraps a fieldset layout
+ * var input1 = new OO.ui.TextInputWidget( {
+ * placeholder: 'Username'
+ * } );
+ * var input2 = new OO.ui.TextInputWidget( {
+ * placeholder: 'Password',
+ * type: 'password'
+ * } );
+ * var submit = new OO.ui.ButtonInputWidget( {
+ * label: 'Submit'
+ * } );
+ *
+ * var fieldset = new OO.ui.FieldsetLayout( {
+ * label: 'A form layout'
+ * } );
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( input1, {
+ * label: 'Username',
+ * align: 'top'
+ * } ),
+ * new OO.ui.FieldLayout( input2, {
+ * label: 'Password',
+ * align: 'top'
+ * } ),
+ * new OO.ui.FieldLayout( submit )
+ * ] );
+ * var form = new OO.ui.FormLayout( {
+ * items: [ fieldset ],
+ * action: '/api/formhandler',
+ * method: 'get'
+ * } )
+ * $( 'body' ).append( form.$element );
+ *
+ * @class
+ * @extends OO.ui.Layout
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [method] HTML form `method` attribute
+ * @cfg {string} [action] HTML form `action` attribute
+ * @cfg {string} [enctype] HTML form `enctype` attribute
+ * @cfg {OO.ui.FieldsetLayout[]} [items] Fieldset layouts to add to the form layout.
+ */
+OO.ui.FormLayout = function OoUiFormLayout( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.FormLayout.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+
+ // Events
+ this.$element.on( 'submit', this.onFormSubmit.bind( this ) );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-formLayout' )
+ .attr( {
+ method: config.method,
+ action: config.action,
+ enctype: config.enctype
+ } );
+ if ( Array.isArray( config.items ) ) {
+ this.addItems( config.items );
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.FormLayout, OO.ui.Layout );
+OO.mixinClass( OO.ui.FormLayout, OO.ui.GroupElement );
+
+/* Events */
+
+/**
+ * A 'submit' event is emitted when the form is submitted.
+ *
+ * @event submit
+ */
+
+/* Static Properties */
+
+OO.ui.FormLayout.static.tagName = 'form';
+
+/* Methods */
+
+/**
+ * Handle form submit events.
+ *
+ * @private
+ * @param {jQuery.Event} e Submit event
+ * @fires submit
+ */
+OO.ui.FormLayout.prototype.onFormSubmit = function () {
+ if ( this.emit( 'submit' ) ) {
+ return false;
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/layouts/IndexLayout.js b/vendor/oojs/oojs-ui/src/layouts/IndexLayout.js
new file mode 100644
index 00000000..4cda00a9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/IndexLayout.js
@@ -0,0 +1,452 @@
+/**
+ * IndexLayouts contain {@link OO.ui.CardLayout card layouts} as well as
+ * {@link OO.ui.TabSelectWidget tabs} that allow users to easily navigate through the cards and
+ * select which one to display. By default, only one card is displayed at a time. When a user
+ * navigates to a new card, the index layout automatically focuses on the first focusable element,
+ * unless the default setting is changed.
+ *
+ * TODO: This class is similar to BookletLayout, we may want to refactor to reduce duplication
+ *
+ * @example
+ * // Example of a IndexLayout that contains two CardLayouts.
+ *
+ * function CardOneLayout( name, config ) {
+ * CardOneLayout.super.call( this, name, config );
+ * this.$element.append( '<p>First card</p>' );
+ * }
+ * OO.inheritClass( CardOneLayout, OO.ui.CardLayout );
+ * CardOneLayout.prototype.setupTabItem = function () {
+ * this.tabItem.setLabel( 'Card One' );
+ * };
+ *
+ * function CardTwoLayout( name, config ) {
+ * CardTwoLayout.super.call( this, name, config );
+ * this.$element.append( '<p>Second card</p>' );
+ * }
+ * OO.inheritClass( CardTwoLayout, OO.ui.CardLayout );
+ * CardTwoLayout.prototype.setupTabItem = function () {
+ * this.tabItem.setLabel( 'Card Two' );
+ * };
+ *
+ * var card1 = new CardOneLayout( 'one' ),
+ * card2 = new CardTwoLayout( 'two' );
+ *
+ * var index = new OO.ui.IndexLayout();
+ *
+ * index.addCards ( [ card1, card2 ] );
+ * $( 'body' ).append( index.$element );
+ *
+ * @class
+ * @extends OO.ui.MenuLayout
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [continuous=false] Show all cards, one after another
+ * @cfg {boolean} [autoFocus=true] Focus on the first focusable element when a new card is displayed.
+ */
+OO.ui.IndexLayout = function OoUiIndexLayout( config ) {
+ // Configuration initialization
+ config = $.extend( {}, config, { menuPosition: 'top' } );
+
+ // Parent constructor
+ OO.ui.IndexLayout.super.call( this, config );
+
+ // Properties
+ this.currentCardName = null;
+ this.cards = {};
+ this.ignoreFocus = false;
+ this.stackLayout = new OO.ui.StackLayout( { continuous: !!config.continuous } );
+ this.$content.append( this.stackLayout.$element );
+ this.autoFocus = config.autoFocus === undefined || !!config.autoFocus;
+
+ this.tabSelectWidget = new OO.ui.TabSelectWidget();
+ this.tabPanel = new OO.ui.PanelLayout();
+ this.$menu.append( this.tabPanel.$element );
+
+ this.toggleMenu( true );
+
+ // Events
+ this.stackLayout.connect( this, { set: 'onStackLayoutSet' } );
+ this.tabSelectWidget.connect( this, { select: 'onTabSelectWidgetSelect' } );
+ if ( this.autoFocus ) {
+ // Event 'focus' does not bubble, but 'focusin' does
+ this.stackLayout.$element.on( 'focusin', this.onStackLayoutFocus.bind( this ) );
+ }
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-indexLayout' );
+ this.stackLayout.$element.addClass( 'oo-ui-indexLayout-stackLayout' );
+ this.tabPanel.$element
+ .addClass( 'oo-ui-indexLayout-tabPanel' )
+ .append( this.tabSelectWidget.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.IndexLayout, OO.ui.MenuLayout );
+
+/* Events */
+
+/**
+ * A 'set' event is emitted when a card is {@link #setCard set} to be displayed by the index layout.
+ * @event set
+ * @param {OO.ui.CardLayout} card Current card
+ */
+
+/**
+ * An 'add' event is emitted when cards are {@link #addCards added} to the index layout.
+ *
+ * @event add
+ * @param {OO.ui.CardLayout[]} card Added cards
+ * @param {number} index Index cards were added at
+ */
+
+/**
+ * A 'remove' event is emitted when cards are {@link #clearCards cleared} or
+ * {@link #removeCards removed} from the index.
+ *
+ * @event remove
+ * @param {OO.ui.CardLayout[]} cards Removed cards
+ */
+
+/* Methods */
+
+/**
+ * Handle stack layout focus.
+ *
+ * @private
+ * @param {jQuery.Event} e Focusin event
+ */
+OO.ui.IndexLayout.prototype.onStackLayoutFocus = function ( e ) {
+ var name, $target;
+
+ // Find the card that an element was focused within
+ $target = $( e.target ).closest( '.oo-ui-cardLayout' );
+ for ( name in this.cards ) {
+ // Check for card match, exclude current card to find only card changes
+ if ( this.cards[ name ].$element[ 0 ] === $target[ 0 ] && name !== this.currentCardName ) {
+ this.setCard( name );
+ break;
+ }
+ }
+};
+
+/**
+ * Handle stack layout set events.
+ *
+ * @private
+ * @param {OO.ui.PanelLayout|null} card The card panel that is now the current panel
+ */
+OO.ui.IndexLayout.prototype.onStackLayoutSet = function ( card ) {
+ var layout = this;
+ if ( card ) {
+ card.scrollElementIntoView( { complete: function () {
+ if ( layout.autoFocus ) {
+ layout.focus();
+ }
+ } } );
+ }
+};
+
+/**
+ * Focus the first input in the current card.
+ *
+ * If no card is selected, the first selectable card will be selected.
+ * If the focus is already in an element on the current card, nothing will happen.
+ * @param {number} [itemIndex] A specific item to focus on
+ */
+OO.ui.IndexLayout.prototype.focus = function ( itemIndex ) {
+ var $input, card,
+ items = this.stackLayout.getItems();
+
+ if ( itemIndex !== undefined && items[ itemIndex ] ) {
+ card = items[ itemIndex ];
+ } else {
+ card = this.stackLayout.getCurrentItem();
+ }
+
+ if ( !card ) {
+ this.selectFirstSelectableCard();
+ card = this.stackLayout.getCurrentItem();
+ }
+ if ( !card ) {
+ return;
+ }
+ // Only change the focus if is not already in the current card
+ if ( !card.$element.find( ':focus' ).length ) {
+ $input = card.$element.find( ':input:first' );
+ if ( $input.length ) {
+ $input[ 0 ].focus();
+ }
+ }
+};
+
+/**
+ * Find the first focusable input in the index layout and focus
+ * on it.
+ */
+OO.ui.IndexLayout.prototype.focusFirstFocusable = function () {
+ var i, len,
+ found = false,
+ items = this.stackLayout.getItems(),
+ checkAndFocus = function () {
+ if ( OO.ui.isFocusableElement( $( this ) ) ) {
+ $( this ).focus();
+ found = true;
+ return false;
+ }
+ };
+
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ if ( found ) {
+ break;
+ }
+ // Find all potentially focusable elements in the item
+ // and check if they are focusable
+ items[i].$element
+ .find( 'input, select, textarea, button, object' )
+ .each( checkAndFocus );
+ }
+};
+
+/**
+ * Handle tab widget select events.
+ *
+ * @private
+ * @param {OO.ui.OptionWidget|null} item Selected item
+ */
+OO.ui.IndexLayout.prototype.onTabSelectWidgetSelect = function ( item ) {
+ if ( item ) {
+ this.setCard( item.getData() );
+ }
+};
+
+/**
+ * Get the card closest to the specified card.
+ *
+ * @param {OO.ui.CardLayout} card Card to use as a reference point
+ * @return {OO.ui.CardLayout|null} Card closest to the specified card
+ */
+OO.ui.IndexLayout.prototype.getClosestCard = function ( card ) {
+ var next, prev, level,
+ cards = this.stackLayout.getItems(),
+ index = $.inArray( card, cards );
+
+ if ( index !== -1 ) {
+ next = cards[ index + 1 ];
+ prev = cards[ index - 1 ];
+ // Prefer adjacent cards at the same level
+ level = this.tabSelectWidget.getItemFromData( card.getName() ).getLevel();
+ if (
+ prev &&
+ level === this.tabSelectWidget.getItemFromData( prev.getName() ).getLevel()
+ ) {
+ return prev;
+ }
+ if (
+ next &&
+ level === this.tabSelectWidget.getItemFromData( next.getName() ).getLevel()
+ ) {
+ return next;
+ }
+ }
+ return prev || next || null;
+};
+
+/**
+ * Get the tabs widget.
+ *
+ * @return {OO.ui.TabSelectWidget} Tabs widget
+ */
+OO.ui.IndexLayout.prototype.getTabs = function () {
+ return this.tabSelectWidget;
+};
+
+/**
+ * Get a card by its symbolic name.
+ *
+ * @param {string} name Symbolic name of card
+ * @return {OO.ui.CardLayout|undefined} Card, if found
+ */
+OO.ui.IndexLayout.prototype.getCard = function ( name ) {
+ return this.cards[ name ];
+};
+
+/**
+ * Get the current card.
+ *
+ * @return {OO.ui.CardLayout|undefined} Current card, if found
+ */
+OO.ui.IndexLayout.prototype.getCurrentCard = function () {
+ var name = this.getCurrentCardName();
+ return name ? this.getCard( name ) : undefined;
+};
+
+/**
+ * Get the symbolic name of the current card.
+ *
+ * @return {string|null} Symbolic name of the current card
+ */
+OO.ui.IndexLayout.prototype.getCurrentCardName = function () {
+ return this.currentCardName;
+};
+
+/**
+ * Add cards to the index layout
+ *
+ * When cards are added with the same names as existing cards, the existing cards will be
+ * automatically removed before the new cards are added.
+ *
+ * @param {OO.ui.CardLayout[]} cards Cards to add
+ * @param {number} index Index of the insertion point
+ * @fires add
+ * @chainable
+ */
+OO.ui.IndexLayout.prototype.addCards = function ( cards, index ) {
+ var i, len, name, card, item, currentIndex,
+ stackLayoutCards = this.stackLayout.getItems(),
+ remove = [],
+ items = [];
+
+ // Remove cards with same names
+ for ( i = 0, len = cards.length; i < len; i++ ) {
+ card = cards[ i ];
+ name = card.getName();
+
+ if ( Object.prototype.hasOwnProperty.call( this.cards, name ) ) {
+ // Correct the insertion index
+ currentIndex = $.inArray( this.cards[ name ], stackLayoutCards );
+ if ( currentIndex !== -1 && currentIndex + 1 < index ) {
+ index--;
+ }
+ remove.push( this.cards[ name ] );
+ }
+ }
+ if ( remove.length ) {
+ this.removeCards( remove );
+ }
+
+ // Add new cards
+ for ( i = 0, len = cards.length; i < len; i++ ) {
+ card = cards[ i ];
+ name = card.getName();
+ this.cards[ card.getName() ] = card;
+ item = new OO.ui.TabOptionWidget( { data: name } );
+ card.setTabItem( item );
+ items.push( item );
+ }
+
+ if ( items.length ) {
+ this.tabSelectWidget.addItems( items, index );
+ this.selectFirstSelectableCard();
+ }
+ this.stackLayout.addItems( cards, index );
+ this.emit( 'add', cards, index );
+
+ return this;
+};
+
+/**
+ * Remove the specified cards from the index layout.
+ *
+ * To remove all cards from the index, you may wish to use the #clearCards method instead.
+ *
+ * @param {OO.ui.CardLayout[]} cards An array of cards to remove
+ * @fires remove
+ * @chainable
+ */
+OO.ui.IndexLayout.prototype.removeCards = function ( cards ) {
+ var i, len, name, card,
+ items = [];
+
+ for ( i = 0, len = cards.length; i < len; i++ ) {
+ card = cards[ i ];
+ name = card.getName();
+ delete this.cards[ name ];
+ items.push( this.tabSelectWidget.getItemFromData( name ) );
+ card.setTabItem( null );
+ }
+ if ( items.length ) {
+ this.tabSelectWidget.removeItems( items );
+ this.selectFirstSelectableCard();
+ }
+ this.stackLayout.removeItems( cards );
+ this.emit( 'remove', cards );
+
+ return this;
+};
+
+/**
+ * Clear all cards from the index layout.
+ *
+ * To remove only a subset of cards from the index, use the #removeCards method.
+ *
+ * @fires remove
+ * @chainable
+ */
+OO.ui.IndexLayout.prototype.clearCards = function () {
+ var i, len,
+ cards = this.stackLayout.getItems();
+
+ this.cards = {};
+ this.currentCardName = null;
+ this.tabSelectWidget.clearItems();
+ for ( i = 0, len = cards.length; i < len; i++ ) {
+ cards[ i ].setTabItem( null );
+ }
+ this.stackLayout.clearItems();
+
+ this.emit( 'remove', cards );
+
+ return this;
+};
+
+/**
+ * Set the current card by symbolic name.
+ *
+ * @fires set
+ * @param {string} name Symbolic name of card
+ */
+OO.ui.IndexLayout.prototype.setCard = function ( name ) {
+ var selectedItem,
+ $focused,
+ card = this.cards[ name ];
+
+ if ( name !== this.currentCardName ) {
+ selectedItem = this.tabSelectWidget.getSelectedItem();
+ if ( selectedItem && selectedItem.getData() !== name ) {
+ this.tabSelectWidget.selectItemByData( name );
+ }
+ if ( card ) {
+ if ( this.currentCardName && this.cards[ this.currentCardName ] ) {
+ this.cards[ this.currentCardName ].setActive( false );
+ // Blur anything focused if the next card doesn't have anything focusable - this
+ // is not needed if the next card has something focusable because once it is focused
+ // this blur happens automatically
+ if ( this.autoFocus && !card.$element.find( ':input' ).length ) {
+ $focused = this.cards[ this.currentCardName ].$element.find( ':focus' );
+ if ( $focused.length ) {
+ $focused[ 0 ].blur();
+ }
+ }
+ }
+ this.currentCardName = name;
+ this.stackLayout.setItem( card );
+ card.setActive( true );
+ this.emit( 'set', card );
+ }
+ }
+};
+
+/**
+ * Select the first selectable card.
+ *
+ * @chainable
+ */
+OO.ui.IndexLayout.prototype.selectFirstSelectableCard = function () {
+ if ( !this.tabSelectWidget.getSelectedItem() ) {
+ this.tabSelectWidget.selectItem( this.tabSelectWidget.getFirstSelectableItem() );
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/layouts/MenuLayout.js b/vendor/oojs/oojs-ui/src/layouts/MenuLayout.js
new file mode 100644
index 00000000..53937cc7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/MenuLayout.js
@@ -0,0 +1,156 @@
+/**
+ * MenuLayouts combine a menu and a content {@link OO.ui.PanelLayout panel}. The menu is positioned relative to the content (after, before, top, or bottom)
+ * and its size is customized with the #menuSize config. The content area will fill all remaining space.
+ *
+ * @example
+ * var menuLayout = new OO.ui.MenuLayout( {
+ * position: 'top'
+ * } ),
+ * menuPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ),
+ * contentPanel = new OO.ui.PanelLayout( { padded: true, expanded: true, scrollable: true } ),
+ * select = new OO.ui.SelectWidget( {
+ * items: [
+ * new OO.ui.OptionWidget( {
+ * data: 'before',
+ * label: 'Before',
+ * } ),
+ * new OO.ui.OptionWidget( {
+ * data: 'after',
+ * label: 'After',
+ * } ),
+ * new OO.ui.OptionWidget( {
+ * data: 'top',
+ * label: 'Top',
+ * } ),
+ * new OO.ui.OptionWidget( {
+ * data: 'bottom',
+ * label: 'Bottom',
+ * } )
+ * ]
+ * } ).on( 'select', function ( item ) {
+ * menuLayout.setMenuPosition( item.getData() );
+ * } );
+ *
+ * menuLayout.$menu.append(
+ * menuPanel.$element.append( '<b>Menu panel</b>', select.$element )
+ * );
+ * menuLayout.$content.append(
+ * contentPanel.$element.append( '<b>Content panel</b>', '<p>Note that the menu is positioned relative to the content panel: top, bottom, after, before.</p>')
+ * );
+ * $( 'body' ).append( menuLayout.$element );
+ *
+ * If menu size needs to be overridden, it can be accomplished using CSS similar to the snippet
+ * below. MenuLayout's CSS will override the appropriate values with 'auto' or '0' to display the
+ * menu correctly. If `menuPosition` is known beforehand, CSS rules corresponding to other positions
+ * may be omitted.
+ *
+ * .oo-ui-menuLayout-menu {
+ * height: 200px;
+ * width: 200px;
+ * }
+ * .oo-ui-menuLayout-content {
+ * top: 200px;
+ * left: 200px;
+ * right: 200px;
+ * bottom: 200px;
+ * }
+ *
+ * @class
+ * @extends OO.ui.Layout
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [showMenu=true] Show menu
+ * @cfg {string} [menuPosition='before'] Position of menu: `top`, `after`, `bottom` or `before`
+ */
+OO.ui.MenuLayout = function OoUiMenuLayout( config ) {
+ // Configuration initialization
+ config = $.extend( {
+ showMenu: true,
+ menuPosition: 'before'
+ }, config );
+
+ // Parent constructor
+ OO.ui.MenuLayout.super.call( this, config );
+
+ /**
+ * Menu DOM node
+ *
+ * @property {jQuery}
+ */
+ this.$menu = $( '<div>' );
+ /**
+ * Content DOM node
+ *
+ * @property {jQuery}
+ */
+ this.$content = $( '<div>' );
+
+ // Initialization
+ this.$menu
+ .addClass( 'oo-ui-menuLayout-menu' );
+ this.$content.addClass( 'oo-ui-menuLayout-content' );
+ this.$element
+ .addClass( 'oo-ui-menuLayout' )
+ .append( this.$content, this.$menu );
+ this.setMenuPosition( config.menuPosition );
+ this.toggleMenu( config.showMenu );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MenuLayout, OO.ui.Layout );
+
+/* Methods */
+
+/**
+ * Toggle menu.
+ *
+ * @param {boolean} showMenu Show menu, omit to toggle
+ * @chainable
+ */
+OO.ui.MenuLayout.prototype.toggleMenu = function ( showMenu ) {
+ showMenu = showMenu === undefined ? !this.showMenu : !!showMenu;
+
+ if ( this.showMenu !== showMenu ) {
+ this.showMenu = showMenu;
+ this.$element
+ .toggleClass( 'oo-ui-menuLayout-showMenu', this.showMenu )
+ .toggleClass( 'oo-ui-menuLayout-hideMenu', !this.showMenu );
+ }
+
+ return this;
+};
+
+/**
+ * Check if menu is visible
+ *
+ * @return {boolean} Menu is visible
+ */
+OO.ui.MenuLayout.prototype.isMenuVisible = function () {
+ return this.showMenu;
+};
+
+/**
+ * Set menu position.
+ *
+ * @param {string} position Position of menu, either `top`, `after`, `bottom` or `before`
+ * @throws {Error} If position value is not supported
+ * @chainable
+ */
+OO.ui.MenuLayout.prototype.setMenuPosition = function ( position ) {
+ this.$element.removeClass( 'oo-ui-menuLayout-' + this.menuPosition );
+ this.menuPosition = position;
+ this.$element.addClass( 'oo-ui-menuLayout-' + position );
+
+ return this;
+};
+
+/**
+ * Get menu position.
+ *
+ * @return {string} Menu position
+ */
+OO.ui.MenuLayout.prototype.getMenuPosition = function () {
+ return this.menuPosition;
+};
diff --git a/vendor/oojs/oojs-ui/src/layouts/PageLayout.js b/vendor/oojs/oojs-ui/src/layouts/PageLayout.js
new file mode 100644
index 00000000..4753b7db
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/PageLayout.js
@@ -0,0 +1,138 @@
+/**
+ * PageLayouts are used within {@link OO.ui.BookletLayout booklet layouts} to create pages that users can select and display
+ * from the booklet's optional {@link OO.ui.OutlineSelectWidget outline} navigation. Pages are usually not instantiated directly,
+ * rather extended to include the required content and functionality.
+ *
+ * Each page must have a unique symbolic name, which is passed to the constructor. In addition, the page's outline
+ * item is customized (with a label, outline level, etc.) using the #setupOutlineItem method. See
+ * {@link OO.ui.BookletLayout BookletLayout} for an example.
+ *
+ * @class
+ * @extends OO.ui.PanelLayout
+ *
+ * @constructor
+ * @param {string} name Unique symbolic name of page
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.PageLayout = function OoUiPageLayout( name, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( name ) && config === undefined ) {
+ config = name;
+ name = config.name;
+ }
+
+ // Configuration initialization
+ config = $.extend( { scrollable: true }, config );
+
+ // Parent constructor
+ OO.ui.PageLayout.super.call( this, config );
+
+ // Properties
+ this.name = name;
+ this.outlineItem = null;
+ this.active = false;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-pageLayout' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PageLayout, OO.ui.PanelLayout );
+
+/* Events */
+
+/**
+ * An 'active' event is emitted when the page becomes active. Pages become active when they are
+ * shown in a booklet layout that is configured to display only one page at a time.
+ *
+ * @event active
+ * @param {boolean} active Page is active
+ */
+
+/* Methods */
+
+/**
+ * Get the symbolic name of the page.
+ *
+ * @return {string} Symbolic name of page
+ */
+OO.ui.PageLayout.prototype.getName = function () {
+ return this.name;
+};
+
+/**
+ * Check if page is active.
+ *
+ * Pages become active when they are shown in a {@link OO.ui.BookletLayout booklet layout} that is configured to display
+ * only one page at a time. Additional CSS is applied to the page's outline item to reflect the active state.
+ *
+ * @return {boolean} Page is active
+ */
+OO.ui.PageLayout.prototype.isActive = function () {
+ return this.active;
+};
+
+/**
+ * Get outline item.
+ *
+ * The outline item allows users to access the page from the booklet's outline
+ * navigation. The outline item itself can be customized (with a label, level, etc.) using the #setupOutlineItem method.
+ *
+ * @return {OO.ui.OutlineOptionWidget|null} Outline option widget
+ */
+OO.ui.PageLayout.prototype.getOutlineItem = function () {
+ return this.outlineItem;
+};
+
+/**
+ * Set or unset the outline item.
+ *
+ * Specify an {@link OO.ui.OutlineOptionWidget outline option} to set it,
+ * or `null` to clear the outline item. To customize the outline item itself (e.g., to set a label or outline
+ * level), use #setupOutlineItem instead of this method.
+ *
+ * @param {OO.ui.OutlineOptionWidget|null} outlineItem Outline option widget, null to clear
+ * @chainable
+ */
+OO.ui.PageLayout.prototype.setOutlineItem = function ( outlineItem ) {
+ this.outlineItem = outlineItem || null;
+ if ( outlineItem ) {
+ this.setupOutlineItem();
+ }
+ return this;
+};
+
+/**
+ * Set up the outline item.
+ *
+ * Use this method to customize the outline item (e.g., to add a label or outline level). To set or unset
+ * the outline item itself (with an {@link OO.ui.OutlineOptionWidget outline option} or `null`), use
+ * the #setOutlineItem method instead.
+ *
+ * @param {OO.ui.OutlineOptionWidget} outlineItem Outline option widget to set up
+ * @chainable
+ */
+OO.ui.PageLayout.prototype.setupOutlineItem = function () {
+ return this;
+};
+
+/**
+ * Set the page to its 'active' state.
+ *
+ * Pages become active when they are shown in a booklet layout that is configured to display only one page at a time. Additional
+ * CSS is applied to the outline item to reflect the page's active state. Outside of the booklet
+ * context, setting the active state on a page does nothing.
+ *
+ * @param {boolean} value Page is active
+ * @fires active
+ */
+OO.ui.PageLayout.prototype.setActive = function ( active ) {
+ active = !!active;
+
+ if ( active !== this.active ) {
+ this.active = active;
+ this.$element.toggleClass( 'oo-ui-pageLayout-active', active );
+ this.emit( 'active', this.active );
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/layouts/PanelLayout.js b/vendor/oojs/oojs-ui/src/layouts/PanelLayout.js
new file mode 100644
index 00000000..9183f597
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/PanelLayout.js
@@ -0,0 +1,55 @@
+/**
+ * PanelLayouts expand to cover the entire area of their parent. They can be configured with scrolling, padding,
+ * and a frame, and are often used together with {@link OO.ui.StackLayout StackLayouts}.
+ *
+ * @example
+ * // Example of a panel layout
+ * var panel = new OO.ui.PanelLayout( {
+ * expanded: false,
+ * framed: true,
+ * padded: true,
+ * $content: $( '<p>A panel layout with padding and a frame.</p>' )
+ * } );
+ * $( 'body' ).append( panel.$element );
+ *
+ * @class
+ * @extends OO.ui.Layout
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [scrollable=false] Allow vertical scrolling
+ * @cfg {boolean} [padded=false] Add padding between the content and the edges of the panel.
+ * @cfg {boolean} [expanded=true] Expand the panel to fill the entire parent element.
+ * @cfg {boolean} [framed=false] Render the panel with a frame to visually separate it from outside content.
+ */
+OO.ui.PanelLayout = function OoUiPanelLayout( config ) {
+ // Configuration initialization
+ config = $.extend( {
+ scrollable: false,
+ padded: false,
+ expanded: true,
+ framed: false
+ }, config );
+
+ // Parent constructor
+ OO.ui.PanelLayout.super.call( this, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-panelLayout' );
+ if ( config.scrollable ) {
+ this.$element.addClass( 'oo-ui-panelLayout-scrollable' );
+ }
+ if ( config.padded ) {
+ this.$element.addClass( 'oo-ui-panelLayout-padded' );
+ }
+ if ( config.expanded ) {
+ this.$element.addClass( 'oo-ui-panelLayout-expanded' );
+ }
+ if ( config.framed ) {
+ this.$element.addClass( 'oo-ui-panelLayout-framed' );
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PanelLayout, OO.ui.Layout );
diff --git a/vendor/oojs/oojs-ui/src/layouts/StackLayout.js b/vendor/oojs/oojs-ui/src/layouts/StackLayout.js
new file mode 100644
index 00000000..58f35d31
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/layouts/StackLayout.js
@@ -0,0 +1,214 @@
+/**
+ * StackLayouts contain a series of {@link OO.ui.PanelLayout panel layouts}. By default, only one panel is displayed
+ * at a time, though the stack layout can also be configured to show all contained panels, one after another,
+ * by setting the #continuous option to 'true'.
+ *
+ * @example
+ * // A stack layout with two panels, configured to be displayed continously
+ * var myStack = new OO.ui.StackLayout( {
+ * items: [
+ * new OO.ui.PanelLayout( {
+ * $content: $( '<p>Panel One</p>' ),
+ * padded: true,
+ * framed: true
+ * } ),
+ * new OO.ui.PanelLayout( {
+ * $content: $( '<p>Panel Two</p>' ),
+ * padded: true,
+ * framed: true
+ * } )
+ * ],
+ * continuous: true
+ * } );
+ * $( 'body' ).append( myStack.$element );
+ *
+ * @class
+ * @extends OO.ui.PanelLayout
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [continuous=false] Show all panels, one after another. By default, only one panel is displayed at a time.
+ * @cfg {OO.ui.Layout[]} [items] Panel layouts to add to the stack layout.
+ */
+OO.ui.StackLayout = function OoUiStackLayout( config ) {
+ // Configuration initialization
+ config = $.extend( { scrollable: true }, config );
+
+ // Parent constructor
+ OO.ui.StackLayout.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+
+ // Properties
+ this.currentItem = null;
+ this.continuous = !!config.continuous;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-stackLayout' );
+ if ( this.continuous ) {
+ this.$element.addClass( 'oo-ui-stackLayout-continuous' );
+ }
+ if ( Array.isArray( config.items ) ) {
+ this.addItems( config.items );
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.StackLayout, OO.ui.PanelLayout );
+OO.mixinClass( OO.ui.StackLayout, OO.ui.GroupElement );
+
+/* Events */
+
+/**
+ * A 'set' event is emitted when panels are {@link #addItems added}, {@link #removeItems removed},
+ * {@link #clearItems cleared} or {@link #setItem displayed}.
+ *
+ * @event set
+ * @param {OO.ui.Layout|null} item Current panel or `null` if no panel is shown
+ */
+
+/* Methods */
+
+/**
+ * Get the current panel.
+ *
+ * @return {OO.ui.Layout|null}
+ */
+OO.ui.StackLayout.prototype.getCurrentItem = function () {
+ return this.currentItem;
+};
+
+/**
+ * Unset the current item.
+ *
+ * @private
+ * @param {OO.ui.StackLayout} layout
+ * @fires set
+ */
+OO.ui.StackLayout.prototype.unsetCurrentItem = function () {
+ var prevItem = this.currentItem;
+ if ( prevItem === null ) {
+ return;
+ }
+
+ this.currentItem = null;
+ this.emit( 'set', null );
+};
+
+/**
+ * Add panel layouts to the stack layout.
+ *
+ * Panels will be added to the end of the stack layout array unless the optional index parameter specifies a different
+ * insertion point. Adding a panel that is already in the stack will move it to the end of the array or the point specified
+ * by the index.
+ *
+ * @param {OO.ui.Layout[]} items Panels to add
+ * @param {number} [index] Index of the insertion point
+ * @chainable
+ */
+OO.ui.StackLayout.prototype.addItems = function ( items, index ) {
+ // Update the visibility
+ this.updateHiddenState( items, this.currentItem );
+
+ // Mixin method
+ OO.ui.GroupElement.prototype.addItems.call( this, items, index );
+
+ if ( !this.currentItem && items.length ) {
+ this.setItem( items[ 0 ] );
+ }
+
+ return this;
+};
+
+/**
+ * Remove the specified panels from the stack layout.
+ *
+ * Removed panels are detached from the DOM, not removed, so that they may be reused. To remove all panels,
+ * you may wish to use the #clearItems method instead.
+ *
+ * @param {OO.ui.Layout[]} items Panels to remove
+ * @chainable
+ * @fires set
+ */
+OO.ui.StackLayout.prototype.removeItems = function ( items ) {
+ // Mixin method
+ OO.ui.GroupElement.prototype.removeItems.call( this, items );
+
+ if ( $.inArray( this.currentItem, items ) !== -1 ) {
+ if ( this.items.length ) {
+ this.setItem( this.items[ 0 ] );
+ } else {
+ this.unsetCurrentItem();
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Clear all panels from the stack layout.
+ *
+ * Cleared panels are detached from the DOM, not removed, so that they may be reused. To remove only
+ * a subset of panels, use the #removeItems method.
+ *
+ * @chainable
+ * @fires set
+ */
+OO.ui.StackLayout.prototype.clearItems = function () {
+ this.unsetCurrentItem();
+ OO.ui.GroupElement.prototype.clearItems.call( this );
+
+ return this;
+};
+
+/**
+ * Show the specified panel.
+ *
+ * If another panel is currently displayed, it will be hidden.
+ *
+ * @param {OO.ui.Layout} item Panel to show
+ * @chainable
+ * @fires set
+ */
+OO.ui.StackLayout.prototype.setItem = function ( item ) {
+ if ( item !== this.currentItem ) {
+ this.updateHiddenState( this.items, item );
+
+ if ( $.inArray( item, this.items ) !== -1 ) {
+ this.currentItem = item;
+ this.emit( 'set', item );
+ } else {
+ this.unsetCurrentItem();
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Update the visibility of all items in case of non-continuous view.
+ *
+ * Ensure all items are hidden except for the selected one.
+ * This method does nothing when the stack is continuous.
+ *
+ * @private
+ * @param {OO.ui.Layout[]} items Item list iterate over
+ * @param {OO.ui.Layout} [selectedItem] Selected item to show
+ */
+OO.ui.StackLayout.prototype.updateHiddenState = function ( items, selectedItem ) {
+ var i, len;
+
+ if ( !this.continuous ) {
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ if ( !selectedItem || selectedItem !== items[ i ] ) {
+ items[ i ].$element.addClass( 'oo-ui-element-hidden' );
+ }
+ }
+ if ( selectedItem ) {
+ selectedItem.$element.removeClass( 'oo-ui-element-hidden' );
+ }
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/outro.js.txt b/vendor/oojs/oojs-ui/src/outro.js.txt
new file mode 100644
index 00000000..4cc1e390
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/outro.js.txt
@@ -0,0 +1 @@
+}( OO ) );
diff --git a/vendor/oojs/oojs-ui/src/styles/Dialog.less b/vendor/oojs/oojs-ui/src/styles/Dialog.less
new file mode 100644
index 00000000..391cefda
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/Dialog.less
@@ -0,0 +1,35 @@
+@import 'common';
+
+.oo-ui-dialog {
+ &-content {
+ > .oo-ui-window {
+ &-head,
+ &-body,
+ &-foot {
+ position: absolute;
+ left: 0;
+ right: 0;
+ overflow: hidden;
+ .oo-ui-box-sizing(border-box);
+ }
+
+ &-head {
+ z-index: 1;
+ top: 0;
+ }
+
+ &-body {
+ z-index: 2;
+ top: 0;
+ bottom: 0;
+ }
+
+ &-foot {
+ z-index: 1;
+ bottom: 0;
+ }
+ }
+ }
+
+ .theme-oo-ui-dialog();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/Element.less b/vendor/oojs/oojs-ui/src/styles/Element.less
new file mode 100644
index 00000000..f8f3389f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/Element.less
@@ -0,0 +1,9 @@
+@import 'common';
+
+.oo-ui-element {
+ &-hidden {
+ display: none !important;
+ }
+
+ .theme-oo-ui-element();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/Layout.less b/vendor/oojs/oojs-ui/src/styles/Layout.less
new file mode 100644
index 00000000..d1ed2f7e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/Layout.less
@@ -0,0 +1,5 @@
+@import 'common';
+
+.oo-ui-layout {
+ .theme-oo-ui-layout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/Tool.less b/vendor/oojs/oojs-ui/src/styles/Tool.less
new file mode 100644
index 00000000..86cf6c38
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/Tool.less
@@ -0,0 +1,5 @@
+@import 'common';
+
+.oo-ui-tool {
+ .theme-oo-ui-tool();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/ToolGroup.less b/vendor/oojs/oojs-ui/src/styles/ToolGroup.less
new file mode 100644
index 00000000..4ba55ea3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/ToolGroup.less
@@ -0,0 +1,21 @@
+@import 'common';
+
+.oo-ui-toolGroup {
+ display: inline-block;
+ vertical-align: middle;
+
+ &-empty {
+ display: none;
+ }
+
+ .oo-ui-tool-link {
+ text-decoration: none;
+
+ .oo-ui-iconElement-icon {
+ background-position: center center;
+ background-repeat: no-repeat;
+ }
+ }
+
+ .theme-oo-ui-toolGroup();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/Toolbar.less b/vendor/oojs/oojs-ui/src/styles/Toolbar.less
new file mode 100644
index 00000000..42eeeaa5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/Toolbar.less
@@ -0,0 +1,52 @@
+@import 'common';
+
+.oo-ui-toolbar {
+ clear: both;
+
+ &-bar {
+ line-height: 1em;
+ position: relative;
+ }
+
+ &-actions {
+ float: right;
+
+ .oo-ui-toolbar {
+ display: inline-block;
+ }
+ }
+
+ &-tools {
+ display: inline;
+ white-space: nowrap;
+
+ .oo-ui-toolbar-narrow & {
+ white-space: normal;
+ }
+
+ // Tools like PopupToolGroup can have a lot of content, which should be wrapped normally
+ .oo-ui-tool {
+ white-space: normal;
+ }
+ }
+
+ &-tools,
+ &-actions,
+ &-shadow {
+ .oo-ui-unselectable();
+ }
+
+ &-actions .oo-ui-popupWidget {
+ .oo-ui-selectable();
+ }
+
+ &-shadow {
+ background-position: left top;
+ background-repeat: repeat-x;
+ position: absolute;
+ width: 100%;
+ pointer-events: none;
+ }
+
+ .theme-oo-ui-toolbar();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/Widget.less b/vendor/oojs/oojs-ui/src/styles/Widget.less
new file mode 100644
index 00000000..107bc424
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/Widget.less
@@ -0,0 +1,5 @@
+@import 'common';
+
+.oo-ui-widget {
+ .theme-oo-ui-widget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/Window.less b/vendor/oojs/oojs-ui/src/styles/Window.less
new file mode 100644
index 00000000..34064684
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/Window.less
@@ -0,0 +1,32 @@
+@import 'common';
+
+.oo-ui-window {
+ &-frame {
+ .oo-ui-box-sizing(border-box);
+ }
+
+ // Content div takes focus when opened, so hide outline
+ &-content:focus {
+ outline: none;
+ }
+
+ &-head,
+ &-foot {
+ .oo-ui-unselectable();
+ }
+
+ &-body {
+ margin: 0;
+ padding: 0;
+ background: none;
+ }
+
+ &-overlay {
+ position: absolute;
+ top: 0;
+ /* @noflip */
+ left: 0;
+ }
+
+ .theme-oo-ui-window();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/WindowManager.less b/vendor/oojs/oojs-ui/src/styles/WindowManager.less
new file mode 100644
index 00000000..bd1e2ab1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/WindowManager.less
@@ -0,0 +1,39 @@
+@import 'common';
+
+.oo-ui-windowManager {
+ &-modal > .oo-ui-dialog {
+ position: fixed;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+
+ &.oo-ui-window-active {
+ width: auto;
+ height: auto;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ padding: 1em;
+ }
+
+ &.oo-ui-window-setup > .oo-ui-window-frame {
+ position: absolute;
+ right: 0;
+ left: 0;
+ margin: auto;
+ overflow: hidden;
+ max-width: 100%;
+ max-height: 100%;
+ }
+ }
+
+ &-fullscreen > .oo-ui-dialog > .oo-ui-window-frame {
+ width: 100%;
+ height: 100%;
+ top: 0;
+ bottom: 0;
+ }
+
+ .theme-oo-ui-windowManager();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/common.less b/vendor/oojs/oojs-ui/src/styles/common.less
new file mode 100644
index 00000000..9663580f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/common.less
@@ -0,0 +1,102 @@
+@import 'theme';
+
+// Variables
+
+// @oo-ui-default-image-ext set during build
+// @oo-ui-distribution set during build
+@oo-ui-default-image-path: 'images';
+
+// Mixins
+
+.oo-ui-background-image( @url ) {
+ background-image: e('/* @embed */') url(~'@{url}');
+}
+
+.oo-ui-background-image-svg2( @svg, @fallback ) when ( @oo-ui-distribution = mixed ) {
+ background-image: url(@fallback);
+ background-image: -webkit-linear-gradient(transparent, transparent), e('/* @embed */') url(@svg);
+ background-image: linear-gradient(transparent, transparent), e('/* @embed */') url(@svg);
+ background-image: -o-linear-gradient(transparent, transparent), url(@fallback);
+}
+.oo-ui-background-image-svg2( @svg, @fallback ) when ( @oo-ui-distribution = vector ) {
+ .oo-ui-background-image(@svg);
+}
+.oo-ui-background-image-svg2( @svg, @fallback ) when ( @oo-ui-distribution = raster ) {
+ .oo-ui-background-image(@fallback);
+}
+
+.oo-ui-background-image-svg( @url-without-extension ) {
+ @svg: '@{url-without-extension}.svg';
+ @fallback: '@{url-without-extension}.@{oo-ui-default-image-ext}';
+ .oo-ui-background-image-svg2(@svg, @fallback);
+}
+
+.oo-ui-force-webkit-gpu() {
+ -webkit-transform: translate3d(0, 0, 0);
+}
+
+.oo-ui-animation( @value1, @value2: X, ... ) {
+ @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
+ -webkit-animation: @value;
+ -moz-animation: @value;
+ -ms-animation: @value;
+ -o-animation: @value;
+ animation: @value;
+}
+
+.oo-ui-transition( @value1, @value2: X, ... ) {
+ @value: ~`"@{arguments}".replace(/[\[\]]|\,\sX/g, '')`;
+ -webkit-transition: @value;
+ -moz-transition: @value;
+ -ms-transition: @value;
+ -o-transition: @value;
+ transition: @value;
+}
+
+.oo-ui-transform( @string ) {
+ -webkit-transform: @string;
+ -moz-transform: @string;
+ -ms-transform: @string;
+ -o-transform: @string;
+ transform: @string;
+}
+
+.oo-ui-box-sizing( @type: border-box ) {
+ -webkit-box-sizing: @type;
+ -moz-box-sizing: @type;
+ box-sizing: @type;
+}
+
+.oo-ui-vertical-gradient( @start: #EEE, @stop: #FFF ) {
+ background: mix(@start, @stop, 50%);
+ filter: e(%("progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='%d', endColorstr='%d')", @start, @stop));
+ background-image: -webkit-gradient(linear, right top, right bottom, color-stop(0%, @start), color-stop(100%, @stop));
+ background-image: -webkit-linear-gradient(top, @start 0%, @stop 100%);
+ background-image: -moz-linear-gradient(top, @start 0%, @stop 100%);
+ background-image: -ms-linear-gradient(top, @start 0%, @stop 100%);
+ background-image: -o-linear-gradient(top, @start 0%, @stop 100%);
+ background-image: linear-gradient(top, @start 0%, @stop 100%);
+}
+
+.oo-ui-unselectable() {
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+.oo-ui-selectable() {
+ -webkit-touch-callout: default;
+ -webkit-user-select: all;
+ -moz-user-select: all;
+ -ms-user-select: all;
+ user-select: all;
+}
+
+.oo-ui-inline-spacing( @spacing, @cancelled-spacing: 0 ) {
+ margin-right: @spacing;
+ &:last-child {
+ margin-right: @cancelled-spacing;
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/core.less b/vendor/oojs/oojs-ui/src/styles/core.less
new file mode 100644
index 00000000..fc1c91f8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/core.less
@@ -0,0 +1,114 @@
+//
+// Base styles.
+//
+// Themes should include this file after defining their variables and mixins.
+//
+
+@import 'common';
+
+/* @noflip */
+.oo-ui-rtl {
+ direction: rtl;
+}
+
+/* @noflip */
+.oo-ui-ltr {
+ direction: ltr;
+}
+
+@import 'Element.less';
+@import 'elements/ButtonElement.less';
+@import 'elements/ClippableElement.less';
+@import 'elements/FlaggedElement.less';
+@import 'elements/DraggableElement.less';
+@import 'elements/DraggableGroupElement.less';
+@import 'elements/GroupElement.less';
+@import 'elements/IconElement.less';
+@import 'elements/IndicatorElement.less';
+@import 'elements/LabelElement.less';
+@import 'elements/LookupElement.less';
+@import 'elements/PopupElement.less';
+@import 'elements/TabIndexedElement.less';
+@import 'elements/TitledElement.less';
+
+@import 'Layout.less';
+@import 'layouts/BookletLayout.less';
+@import 'layouts/IndexLayout.less';
+@import 'layouts/FieldLayout.less';
+@import 'layouts/ActionFieldLayout.less';
+@import 'layouts/FieldsetLayout.less';
+@import 'layouts/FormLayout.less';
+@import 'layouts/MenuLayout.less';
+@import 'layouts/PanelLayout.less';
+@import 'layouts/CardLayout.less';
+@import 'layouts/PageLayout.less';
+@import 'layouts/StackLayout.less';
+
+@import 'Tool.less';
+@import 'tools/PopupTool.less';
+@import 'tools/ToolGroupTool.less';
+
+@import 'ToolGroup.less';
+@import 'toolgroups/BarToolGroup.less';
+@import 'toolgroups/PopupToolGroup.less';
+@import 'toolgroups/ListToolGroup.less';
+@import 'toolgroups/MenuToolGroup.less';
+
+@import 'Toolbar.less';
+
+@import 'Widget.less';
+@import 'widgets/SelectWidget.less';
+@import 'widgets/OptionWidget.less';
+@import 'widgets/DecoratedOptionWidget.less';
+@import 'widgets/ButtonSelectWidget.less';
+@import 'widgets/RadioSelectWidget.less';
+@import 'widgets/ButtonOptionWidget.less';
+@import 'widgets/RadioOptionWidget.less';
+
+@import 'widgets/LabelWidget.less';
+@import 'widgets/IconWidget.less';
+@import 'widgets/IndicatorWidget.less';
+
+@import 'widgets/ButtonWidget.less';
+@import 'widgets/ButtonGroupWidget.less';
+
+@import 'widgets/ToggleWidget.less';
+@import 'widgets/ToggleButtonWidget.less';
+@import 'widgets/ToggleSwitchWidget.less';
+
+@import 'widgets/ProgressBarWidget.less';
+
+@import 'widgets/ActionWidget.less';
+
+@import 'widgets/PopupWidget.less';
+@import 'widgets/PopupButtonWidget.less';
+
+@import 'widgets/InputWidget.less';
+@import 'widgets/ButtonInputWidget.less';
+@import 'widgets/CheckboxInputWidget.less';
+@import 'widgets/DropdownInputWidget.less';
+@import 'widgets/RadioInputWidget.less';
+@import 'widgets/TextInputWidget.less';
+
+@import 'widgets/MenuSelectWidget.less';
+@import 'widgets/MenuOptionWidget.less';
+@import 'widgets/MenuSectionOptionWidget.less';
+@import 'widgets/TextInputMenuSelectWidget.less';
+@import 'widgets/DropdownWidget.less';
+
+@import 'widgets/OutlineSelectWidget.less';
+@import 'widgets/OutlineOptionWidget.less';
+@import 'widgets/OutlineControlsWidget.less';
+
+@import 'widgets/TabSelectWidget.less';
+@import 'widgets/TabOptionWidget.less';
+
+@import 'widgets/ComboBoxWidget.less';
+@import 'widgets/SearchWidget.less';
+
+@import 'Window.less';
+@import 'Dialog.less';
+@import 'dialogs/MessageDialog.less';
+@import 'dialogs/ProcessDialog.less';
+
+@import 'WindowManager.less';
diff --git a/vendor/oojs/oojs-ui/src/styles/dialogs/MessageDialog.less b/vendor/oojs/oojs-ui/src/styles/dialogs/MessageDialog.less
new file mode 100644
index 00000000..e50029f9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/dialogs/MessageDialog.less
@@ -0,0 +1,45 @@
+@import '../common';
+
+.oo-ui-messageDialog {
+ &-actions {
+ &-horizontal {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+
+ .oo-ui-actionWidget {
+ display: table-cell;
+ width: 1%;
+ }
+ }
+
+ &-vertical {
+ display: block;
+
+ .oo-ui-actionWidget {
+ display: block;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+
+ .oo-ui-actionWidget {
+ position: relative;
+ text-align: center;
+
+ .oo-ui-buttonElement-button {
+ display: block;
+ }
+
+ .oo-ui-labelElement-label {
+ position: relative;
+ top: auto;
+ bottom: auto;
+ display: inline;
+ white-space: nowrap;
+ }
+ }
+ }
+
+ .theme-oo-ui-messageDialog();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/dialogs/ProcessDialog.less b/vendor/oojs/oojs-ui/src/styles/dialogs/ProcessDialog.less
new file mode 100644
index 00000000..379588ae
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/dialogs/ProcessDialog.less
@@ -0,0 +1,52 @@
+@import '../common';
+
+.oo-ui-processDialog {
+ &-location {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ &-title {
+ display: inline;
+ padding: 0;
+ }
+
+ &-actions {
+ &-safe,
+ &-primary,
+ &-other {
+ .oo-ui-actionWidget {
+ white-space: nowrap;
+ }
+ }
+
+ &-safe,
+ &-primary {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ }
+
+ &-safe {
+ left: 0;
+ }
+
+ &-primary {
+ right: 0;
+ }
+ }
+
+ &-errors {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 2;
+ overflow-x: hidden;
+ overflow-y: auto;
+ }
+
+ .theme-oo-ui-processDialog();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/ButtonElement.less b/vendor/oojs/oojs-ui/src/styles/elements/ButtonElement.less
new file mode 100644
index 00000000..248772e5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/ButtonElement.less
@@ -0,0 +1,62 @@
+@import '../common';
+
+.oo-ui-buttonElement {
+ > .oo-ui-buttonElement-button {
+ cursor: pointer;
+ display: inline-block;
+ vertical-align: middle;
+ font: inherit;
+ white-space: nowrap;
+ .oo-ui-unselectable();
+
+ > .oo-ui-iconElement-icon,
+ > .oo-ui-indicatorElement-indicator {
+ display: none;
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
+ cursor: default;
+ }
+
+ &.oo-ui-indicatorElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator,
+ &.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ display: inline-block;
+ vertical-align: middle;
+ background-position: center center;
+ background-repeat: no-repeat;
+ }
+
+ &-frameless {
+ display: inline-block;
+ position: relative;
+
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ display: inline-block;
+ vertical-align: middle;
+ }
+ }
+
+ &-framed {
+ > .oo-ui-buttonElement-button {
+ display: inline-block;
+ vertical-align: top;
+ text-align: center;
+ }
+
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ &.oo-ui-widget-disabled {
+ > .oo-ui-buttonElement-button,
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ cursor: default;
+ }
+ }
+ }
+
+ .theme-oo-ui-buttonElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/ClippableElement.less b/vendor/oojs/oojs-ui/src/styles/elements/ClippableElement.less
new file mode 100644
index 00000000..2a661e93
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/ClippableElement.less
@@ -0,0 +1,7 @@
+@import '../common';
+
+.oo-ui-clippableElement-clippable {
+ .oo-ui-box-sizing(border-box);
+
+ .theme-oo-ui-clippableElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/DraggableElement.less b/vendor/oojs/oojs-ui/src/styles/elements/DraggableElement.less
new file mode 100644
index 00000000..3f382766
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/DraggableElement.less
@@ -0,0 +1,22 @@
+@import '../common';
+
+.oo-ui-draggableElement {
+ cursor: -webkit-grab -moz-grab, url(images/grab.cur), move;
+
+ &-dragging {
+ cursor: -webkit-grabbing -moz-grabbing, url(images/grabbing.cur), move;
+ background: rgba( 0, 0, 0, 0.2 );
+ opacity: 0.4;
+ }
+
+ /*
+ * HACK: In order to style horizontally, we must override
+ * OO.ui.OptionWidget's display rule that is currently set
+ * to be 'block'
+ */
+ .oo-ui-draggableGroupElement-horizontal &.oo-ui-optionWidget {
+ display: inline-block;
+ }
+
+ .theme-oo-ui-draggableElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/DraggableGroupElement.less b/vendor/oojs/oojs-ui/src/styles/elements/DraggableGroupElement.less
new file mode 100644
index 00000000..07e08594
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/DraggableGroupElement.less
@@ -0,0 +1,11 @@
+@import '../common';
+
+.oo-ui-draggableGroupElement {
+ &-placeholder {
+ position: absolute;
+ display: block;
+ background: rgba( 0, 0, 0, 0.4 );
+ }
+
+ .theme-oo-ui-draggableGroupElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/FlaggedElement.less b/vendor/oojs/oojs-ui/src/styles/elements/FlaggedElement.less
new file mode 100644
index 00000000..6e9202e9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/FlaggedElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-flaggedElement {
+ .theme-oo-ui-flaggedElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/GroupElement.less b/vendor/oojs/oojs-ui/src/styles/elements/GroupElement.less
new file mode 100644
index 00000000..159d32cc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/GroupElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-groupElement {
+ .theme-oo-ui-groupElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/IconElement.less b/vendor/oojs/oojs-ui/src/styles/elements/IconElement.less
new file mode 100644
index 00000000..5d90fda6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/IconElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-iconElement {
+ .theme-oo-ui-iconElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/IndicatorElement.less b/vendor/oojs/oojs-ui/src/styles/elements/IndicatorElement.less
new file mode 100644
index 00000000..d400803c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/IndicatorElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-indicatorElement {
+ .theme-oo-ui-indicatorElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/LabelElement.less b/vendor/oojs/oojs-ui/src/styles/elements/LabelElement.less
new file mode 100644
index 00000000..c47ecdc4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/LabelElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-labelElement {
+ .theme-oo-ui-labelElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/LookupElement.less b/vendor/oojs/oojs-ui/src/styles/elements/LookupElement.less
new file mode 100644
index 00000000..0ab29f72
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/LookupElement.less
@@ -0,0 +1,10 @@
+@import '../common';
+
+.oo-ui-lookupElement {
+ > .oo-ui-menuSelectWidget {
+ z-index: 1;
+ width: 100%;
+ }
+
+ .theme-oo-ui-lookupElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/PopupElement.less b/vendor/oojs/oojs-ui/src/styles/elements/PopupElement.less
new file mode 100644
index 00000000..12b80daf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/PopupElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-popupElement {
+ .theme-oo-ui-popupElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/TabIndexedElement.less b/vendor/oojs/oojs-ui/src/styles/elements/TabIndexedElement.less
new file mode 100644
index 00000000..f894d369
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/TabIndexedElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-tabIndexedElement {
+ .theme-oo-ui-tabIndexedElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/elements/TitledElement.less b/vendor/oojs/oojs-ui/src/styles/elements/TitledElement.less
new file mode 100644
index 00000000..f8a15b68
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/elements/TitledElement.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-titledElement {
+ .theme-oo-ui-titledElement();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/images/grab.cur b/vendor/oojs/oojs-ui/src/styles/images/grab.cur
new file mode 100644
index 00000000..fba3ddc8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/images/grab.cur
Binary files differ
diff --git a/vendor/oojs/oojs-ui/src/styles/images/grabbing.cur b/vendor/oojs/oojs-ui/src/styles/images/grabbing.cur
new file mode 100644
index 00000000..41aaa62a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/images/grabbing.cur
Binary files differ
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/ActionFieldLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/ActionFieldLayout.less
new file mode 100644
index 00000000..2d4c2a66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/ActionFieldLayout.less
@@ -0,0 +1,26 @@
+@import '../common';
+
+.oo-ui-actionFieldLayout {
+ &-field {
+ display: table;
+ table-layout: fixed;
+ width: 100%;
+ }
+
+ &-input,
+ &-button {
+ display: table-cell;
+ vertical-align: middle;
+ }
+
+ &-input {
+ padding-right: 1em;
+ }
+
+ &-button {
+ width: 1%;
+ white-space: nowrap;
+ }
+
+ .theme-oo-ui-actionFieldLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/BookletLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/BookletLayout.less
new file mode 100644
index 00000000..b31cb6d7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/BookletLayout.less
@@ -0,0 +1,43 @@
+@import '../common';
+
+.oo-ui-bookletLayout {
+ &-stackLayout {
+ &.oo-ui-stackLayout-continuous > .oo-ui-panelLayout-scrollable {
+ overflow-y: hidden;
+ }
+
+ > .oo-ui-panelLayout {
+ width: 100%;
+
+ .oo-ui-box-sizing(border-box);
+
+ &-scrollable {
+ overflow-y: auto;
+ }
+
+ &-padded {
+ padding: 2em;
+ }
+ }
+ }
+
+ &-outlinePanel {
+ &-editable > .oo-ui-outlineSelectWidget {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 3em;
+ overflow-y: auto;
+ }
+
+ > .oo-ui-outlineControlsWidget {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ }
+ }
+
+ .theme-oo-ui-bookletLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/CardLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/CardLayout.less
new file mode 100644
index 00000000..4fdb20b2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/CardLayout.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-cardLayout {
+ .theme-oo-ui-cardLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/FieldLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/FieldLayout.less
new file mode 100644
index 00000000..799f9f4b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/FieldLayout.less
@@ -0,0 +1,59 @@
+@import '../common';
+
+.oo-ui-fieldLayout {
+ display: block;
+
+ &:before,
+ &:after {
+ content: " ";
+ display: table;
+ }
+
+ &:after {
+ clear: both;
+ }
+
+ &.oo-ui-fieldLayout-align-left,
+ &.oo-ui-fieldLayout-align-right {
+ > .oo-ui-fieldLayout-body {
+ > .oo-ui-labelElement-label,
+ > .oo-ui-fieldLayout-field {
+ display: block;
+ float: left;
+ }
+ }
+ }
+
+ &.oo-ui-fieldLayout-align-right > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ text-align: right;
+ }
+
+ &.oo-ui-fieldLayout-align-inline > .oo-ui-fieldLayout-body {
+ display: table;
+
+ > .oo-ui-labelElement-label,
+ > .oo-ui-fieldLayout-field {
+ display: table-cell;
+ vertical-align: middle;
+ }
+ }
+
+ &.oo-ui-labelElement.oo-ui-fieldLayout-align-top > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ display: inline-block;
+ }
+
+ > .oo-ui-fieldLayout-help {
+ float: right;
+
+ > .oo-ui-popupWidget > .oo-ui-popupWidget-popup {
+ z-index: 1;
+ }
+
+ .oo-ui-fieldLayout-help-content {
+ padding: 0.5em 0.75em;
+ line-height: 1.5em;
+ }
+ }
+
+ .theme-oo-ui-fieldLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/FieldsetLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/FieldsetLayout.less
new file mode 100644
index 00000000..726b9310
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/FieldsetLayout.less
@@ -0,0 +1,31 @@
+@import '../common';
+
+.oo-ui-fieldsetLayout {
+ position: relative;
+
+ &.oo-ui-iconElement > .oo-ui-iconElement-icon {
+ display: block;
+ position: absolute;
+ background-position: center center;
+ background-repeat: no-repeat;
+ }
+
+ &.oo-ui-labelElement > .oo-ui-labelElement-label {
+ display: inline-block;
+ }
+
+ > .oo-ui-fieldsetLayout-help {
+ float: right;
+
+ > .oo-ui-popupWidget > .oo-ui-popupWidget-popup {
+ z-index: 1;
+ }
+
+ .oo-ui-fieldsetLayout-help-content {
+ padding: 0.5em 0.75em;
+ line-height: 1.5em;
+ }
+ }
+
+ .theme-oo-ui-fieldsetLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/FormLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/FormLayout.less
new file mode 100644
index 00000000..7354e4bd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/FormLayout.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-formLayout {
+ .theme-oo-ui-formLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/IndexLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/IndexLayout.less
new file mode 100644
index 00000000..63f3f146
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/IndexLayout.less
@@ -0,0 +1,13 @@
+@import '../common';
+
+.oo-ui-indexLayout {
+ > .oo-ui-menuLayout-menu {
+ height: 3em;
+ }
+
+ > .oo-ui-menuLayout-content {
+ top: 3em;
+ }
+
+ .theme-oo-ui-indexLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/MenuLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/MenuLayout.less
new file mode 100644
index 00000000..c5080e2b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/MenuLayout.less
@@ -0,0 +1,108 @@
+@import '../common';
+
+.oo-ui-menuLayout {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+
+ &-menu,
+ &-content {
+ position: absolute;
+ .oo-ui-transition(all ease-in-out 200ms);
+ }
+
+ // These are overridden with 'auto' or '0' later
+ &-menu {
+ height: 18em;
+ width: 18em;
+ }
+
+ // These are overridden with 'auto' or '0' later
+ &-content {
+ top: 18em;
+ left: 18em;
+ right: 18em;
+ bottom: 18em;
+ }
+
+ &.oo-ui-menuLayout-hideMenu {
+ .oo-ui-menuLayout-menu {
+ width: 0 !important;
+ height: 0 !important;
+ overflow: hidden;
+ }
+
+ .oo-ui-menuLayout-content {
+ top: 0 !important;
+ left: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ }
+ }
+
+ &.oo-ui-menuLayout-showMenu {
+ &.oo-ui-menuLayout-top {
+ .oo-ui-menuLayout-menu {
+ width: auto !important;
+ left: 0;
+ top: 0;
+ right: 0;
+ }
+
+ .oo-ui-menuLayout-content {
+ right: 0 !important;
+ bottom: 0 !important;
+ left: 0 !important;
+ }
+ }
+
+ &.oo-ui-menuLayout-after {
+ .oo-ui-menuLayout-menu {
+ height: auto !important;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ }
+
+ .oo-ui-menuLayout-content {
+ bottom: 0 !important;
+ left: 0 !important;
+ top: 0 !important;
+ }
+ }
+
+ &.oo-ui-menuLayout-bottom {
+ .oo-ui-menuLayout-menu {
+ width: auto !important;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ }
+
+ .oo-ui-menuLayout-content {
+ left: 0 !important;
+ top: 0 !important;
+ right: 0 !important;
+ }
+ }
+
+ &.oo-ui-menuLayout-before {
+ .oo-ui-menuLayout-menu {
+ height: auto !important;
+ bottom: 0;
+ left: 0;
+ top: 0;
+ }
+
+ .oo-ui-menuLayout-content {
+ top: 0 !important;
+ right: 0 !important;
+ bottom: 0 !important;
+ }
+ }
+ }
+
+ .theme-oo-ui-menuLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/PageLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/PageLayout.less
new file mode 100644
index 00000000..91aa4571
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/PageLayout.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-pageLayout {
+ .theme-oo-ui-pageLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/PanelLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/PanelLayout.less
new file mode 100644
index 00000000..ed488ed7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/PanelLayout.less
@@ -0,0 +1,19 @@
+@import '../common';
+
+.oo-ui-panelLayout {
+ position: relative;
+
+ &-scrollable {
+ overflow-y: auto;
+ }
+
+ &-expanded {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ }
+
+ .theme-oo-ui-panelLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/layouts/StackLayout.less b/vendor/oojs/oojs-ui/src/styles/layouts/StackLayout.less
new file mode 100644
index 00000000..6dd4fa79
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/layouts/StackLayout.less
@@ -0,0 +1,10 @@
+@import '../common';
+
+.oo-ui-stackLayout {
+ &-continuous > .oo-ui-panelLayout {
+ display: block;
+ position: relative;
+ }
+
+ .theme-oo-ui-stackLayout();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/theme.less b/vendor/oojs/oojs-ui/src/styles/theme.less
new file mode 100644
index 00000000..2cd4c0e3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/theme.less
@@ -0,0 +1,91 @@
+//
+// Blank theme mixins.
+//
+// Base styles invoke these mixins at the end of their definitions. Override these mixins to add
+// additional rules to the base styles.
+//
+
+.theme-oo-ui-layout () {}
+.theme-oo-ui-element () {}
+.theme-oo-ui-widget () {}
+.theme-oo-ui-window () {}
+.theme-oo-ui-dialog () {}
+.theme-oo-ui-windowManager () {}
+
+.theme-oo-ui-buttonElement () {}
+.theme-oo-ui-clippableElement () {}
+.theme-oo-ui-draggableElement () {}
+.theme-oo-ui-flaggedElement () {}
+.theme-oo-ui-groupElement () {}
+.theme-oo-ui-draggableGroupElement () {}
+.theme-oo-ui-iconElement () {}
+.theme-oo-ui-indicatorElement () {}
+.theme-oo-ui-labelElement () {}
+.theme-oo-ui-lookupElement () {}
+.theme-oo-ui-popupElement () {}
+.theme-oo-ui-tabIndexedElement () {}
+.theme-oo-ui-titledElement () {}
+
+.theme-oo-ui-tool () {}
+.theme-oo-ui-toolbar () {}
+.theme-oo-ui-toolGroup () {}
+.theme-oo-ui-messageDialog () {}
+.theme-oo-ui-processDialog () {}
+
+.theme-oo-ui-bookletLayout () {}
+.theme-oo-ui-indexLayout () {}
+.theme-oo-ui-fieldLayout () {}
+.theme-oo-ui-actionFieldLayout () {}
+.theme-oo-ui-fieldsetLayout () {}
+.theme-oo-ui-formLayout () {}
+.theme-oo-ui-menuLayout () {}
+.theme-oo-ui-panelLayout () {}
+.theme-oo-ui-cardLayout () {}
+.theme-oo-ui-pageLayout () {}
+.theme-oo-ui-stackLayout () {}
+
+.theme-oo-ui-barToolGroup () {}
+.theme-oo-ui-popupToolGroup () {}
+.theme-oo-ui-listToolGroup () {}
+.theme-oo-ui-menuToolGroup () {}
+
+.theme-oo-ui-popupTool () {}
+.theme-oo-ui-toolGroupTool () {}
+
+.theme-oo-ui-outlineControlsWidget () {}
+.theme-oo-ui-toggleWidget () {}
+
+.theme-oo-ui-buttonGroupWidget () {}
+.theme-oo-ui-buttonWidget () {}
+.theme-oo-ui-actionWidget () {}
+.theme-oo-ui-popupButtonWidget () {}
+.theme-oo-ui-toggleButtonWidget () {}
+.theme-oo-ui-iconWidget () {}
+.theme-oo-ui-indicatorWidget () {}
+.theme-oo-ui-dropdownWidget () {}
+.theme-oo-ui-inputWidget () {}
+.theme-oo-ui-buttonInputWidget () {}
+.theme-oo-ui-checkboxInputWidget () {}
+.theme-oo-ui-dropdownInputWidget () {}
+.theme-oo-ui-radioInputWidget () {}
+.theme-oo-ui-textInputWidget () {}
+.theme-oo-ui-comboBoxWidget () {}
+.theme-oo-ui-labelWidget () {}
+.theme-oo-ui-optionWidget () {}
+.theme-oo-ui-decoratedOptionWidget () {}
+.theme-oo-ui-buttonOptionWidget () {}
+.theme-oo-ui-radioOptionWidget () {}
+.theme-oo-ui-menuOptionWidget () {}
+.theme-oo-ui-menuSectionOptionWidget () {}
+.theme-oo-ui-outlineOptionWidget () {}
+.theme-oo-ui-tabOptionWidget () {}
+.theme-oo-ui-popupWidget () {}
+.theme-oo-ui-searchWidget () {}
+.theme-oo-ui-selectWidget () {}
+.theme-oo-ui-buttonSelectWidget () {}
+.theme-oo-ui-radioSelectWidget () {}
+.theme-oo-ui-menuSelectWidget () {}
+.theme-oo-ui-textInputMenuSelectWidget () {}
+.theme-oo-ui-outlineSelectWidget () {}
+.theme-oo-ui-tabSelectWidget () {}
+.theme-oo-ui-toggleSwitchWidget () {}
diff --git a/vendor/oojs/oojs-ui/src/styles/toolgroups/BarToolGroup.less b/vendor/oojs/oojs-ui/src/styles/toolgroups/BarToolGroup.less
new file mode 100644
index 00000000..44c3bb8c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/toolgroups/BarToolGroup.less
@@ -0,0 +1,51 @@
+@import '../common';
+
+.oo-ui-barToolGroup {
+ > .oo-ui-iconElement-icon,
+ > .oo-ui-labelElement-label {
+ display: none;
+ }
+
+ &.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ > .oo-ui-tool-link {
+ cursor: pointer;
+ }
+ }
+
+ > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ display: inline-block;
+ position: relative;
+ vertical-align: top;
+
+ > .oo-ui-tool-link {
+ display: block;
+
+ .oo-ui-tool-accel {
+ display: none;
+ }
+ }
+
+ &.oo-ui-iconElement > .oo-ui-tool-link {
+ .oo-ui-iconElement-icon {
+ display: inline-block;
+ vertical-align: top;
+ }
+
+ .oo-ui-tool-title {
+ display: none;
+ }
+ }
+
+ &.oo-ui-iconElement.oo-ui-tool-with-label > .oo-ui-tool-link {
+ .oo-ui-tool-title {
+ display: inline;
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-tool-link {
+ cursor: default;
+ }
+ }
+
+ .theme-oo-ui-barToolGroup();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/toolgroups/ListToolGroup.less b/vendor/oojs/oojs-ui/src/styles/toolgroups/ListToolGroup.less
new file mode 100644
index 00000000..f3c745a3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/toolgroups/ListToolGroup.less
@@ -0,0 +1,21 @@
+@import '../common';
+
+.oo-ui-listToolGroup {
+ .oo-ui-tool {
+ display: block;
+
+ .oo-ui-box-sizing(border-box);
+
+ &-link {
+ cursor: pointer;
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-tool-link {
+ cursor: default;
+ }
+ }
+ }
+
+ .theme-oo-ui-listToolGroup();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/toolgroups/MenuToolGroup.less b/vendor/oojs/oojs-ui/src/styles/toolgroups/MenuToolGroup.less
new file mode 100644
index 00000000..cfbbe0b6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/toolgroups/MenuToolGroup.less
@@ -0,0 +1,19 @@
+@import '../common';
+
+.oo-ui-menuToolGroup {
+ .oo-ui-tool {
+ display: block;
+
+ &-link {
+ cursor: pointer;
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-tool-link {
+ cursor: default;
+ }
+ }
+ }
+
+ .theme-oo-ui-menuToolGroup();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/toolgroups/PopupToolGroup.less b/vendor/oojs/oojs-ui/src/styles/toolgroups/PopupToolGroup.less
new file mode 100644
index 00000000..1b54bea5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/toolgroups/PopupToolGroup.less
@@ -0,0 +1,73 @@
+@import '../common';
+
+.oo-ui-popupToolGroup {
+ position: relative;
+
+ &-handle {
+ display: block;
+ cursor: pointer;
+
+ .oo-ui-indicatorElement-indicator,
+ .oo-ui-iconElement-icon {
+ position: absolute;
+ background-position: center center;
+ background-repeat: no-repeat;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-popupToolGroup-handle {
+ cursor: default;
+ }
+ }
+
+ .oo-ui-toolGroup-tools {
+ display: none;
+ position: absolute;
+ z-index: 4;
+
+ .oo-ui-iconElement-icon {
+ background-repeat: no-repeat;
+ background-position: center center;
+ }
+ }
+
+ &-active.oo-ui-widget-enabled {
+ > .oo-ui-toolGroup-tools {
+ display: block;
+ }
+ }
+
+ &-left > .oo-ui-toolGroup-tools {
+ left: 0;
+ }
+
+ &-right > .oo-ui-toolGroup-tools {
+ right: 0;
+ }
+
+ .oo-ui-tool-link {
+ display: table;
+ width: 100%;
+ vertical-align: middle;
+ white-space: nowrap;
+
+ .oo-ui-iconElement-icon,
+ .oo-ui-tool-accel,
+ .oo-ui-tool-title {
+ display: table-cell;
+ vertical-align: middle;
+ }
+
+ .oo-ui-tool-accel {
+ text-align: right;
+ }
+
+ .oo-ui-tool-accel:not(:empty) {
+ // Push away from tool's title
+ padding-left: 3em;
+ }
+ }
+
+ .theme-oo-ui-popupToolGroup();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/tools/PopupTool.less b/vendor/oojs/oojs-ui/src/styles/tools/PopupTool.less
new file mode 100644
index 00000000..df90302b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/tools/PopupTool.less
@@ -0,0 +1,12 @@
+@import '../common';
+
+.oo-ui-popupTool {
+ .oo-ui-popupWidget {
+ &-popup,
+ &-anchor {
+ z-index: 4;
+ }
+ }
+
+ .theme-oo-ui-popupTool();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/tools/ToolGroupTool.less b/vendor/oojs/oojs-ui/src/styles/tools/ToolGroupTool.less
new file mode 100644
index 00000000..753d4e8a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/tools/ToolGroupTool.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-toolGroupTool {
+ .theme-oo-ui-toolGroupTool();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ActionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ActionWidget.less
new file mode 100644
index 00000000..39ed296f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ActionWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-actionWidget {
+ .theme-oo-ui-actionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ButtonGroupWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonGroupWidget.less
new file mode 100644
index 00000000..eee220d4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonGroupWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-buttonGroupWidget {
+ .theme-oo-ui-buttonGroupWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ButtonInputWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonInputWidget.less
new file mode 100644
index 00000000..5de860eb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonInputWidget.less
@@ -0,0 +1,8 @@
+@import '../common';
+
+.oo-ui-buttonInputWidget {
+ display: inline-block;
+ vertical-align: middle;
+
+ .theme-oo-ui-buttonInputWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ButtonOptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonOptionWidget.less
new file mode 100644
index 00000000..fe214090
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonOptionWidget.less
@@ -0,0 +1,18 @@
+@import '../common';
+
+.oo-ui-buttonOptionWidget {
+ display: inline-block;
+
+ .oo-ui-buttonElement-button {
+ position: relative;
+ }
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon,
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ position: static;
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ .theme-oo-ui-buttonOptionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ButtonSelectWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonSelectWidget.less
new file mode 100644
index 00000000..d0430db5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonSelectWidget.less
@@ -0,0 +1,8 @@
+@import '../common';
+
+.oo-ui-buttonSelectWidget {
+ display: inline-block;
+ white-space: nowrap;
+
+ .theme-oo-ui-buttonSelectWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ButtonWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonWidget.less
new file mode 100644
index 00000000..c33149f2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ButtonWidget.less
@@ -0,0 +1,8 @@
+@import '../common';
+
+.oo-ui-buttonWidget {
+ display: inline-block;
+ vertical-align: middle;
+
+ .theme-oo-ui-buttonWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/CheckboxInputWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/CheckboxInputWidget.less
new file mode 100644
index 00000000..608c9a6b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/CheckboxInputWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-checkboxInputWidget {
+ .theme-oo-ui-checkboxInputWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ComboBoxWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ComboBoxWidget.less
new file mode 100644
index 00000000..1b393c5b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ComboBoxWidget.less
@@ -0,0 +1,13 @@
+@import '../common';
+
+.oo-ui-comboBoxWidget {
+ display: inline-block;
+ position: relative;
+
+ > .oo-ui-menuSelectWidget {
+ z-index: 1;
+ width: 100%;
+ }
+
+ .theme-oo-ui-comboBoxWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/DecoratedOptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/DecoratedOptionWidget.less
new file mode 100644
index 00000000..a31d0670
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/DecoratedOptionWidget.less
@@ -0,0 +1,12 @@
+@import '../common';
+
+.oo-ui-decoratedOptionWidget {
+ .oo-ui-iconElement-icon,
+ .oo-ui-indicatorElement-indicator {
+ position: absolute;
+ background-repeat: no-repeat;
+ background-position: center center;
+ }
+
+ .theme-oo-ui-decoratedOptionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/DropdownInputWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/DropdownInputWidget.less
new file mode 100644
index 00000000..8b7bb7cf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/DropdownInputWidget.less
@@ -0,0 +1,17 @@
+@import '../common';
+
+.oo-ui-dropdownInputWidget {
+ position: relative;
+ // Necessary for proper alignment when used with display: inline-block
+ vertical-align: middle;
+ .oo-ui-box-sizing(border-box);
+
+ select {
+ display: inline-block;
+ width: 100%;
+ resize: none;
+ .oo-ui-box-sizing(border-box);
+ }
+
+ .theme-oo-ui-dropdownInputWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/DropdownWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/DropdownWidget.less
new file mode 100644
index 00000000..d934cd4b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/DropdownWidget.less
@@ -0,0 +1,33 @@
+@import '../common';
+
+.oo-ui-dropdownWidget {
+ display: inline-block;
+ position: relative;
+
+ &-handle {
+ width: 100%;
+ display: inline-block;
+ cursor: pointer;
+
+ .oo-ui-unselectable();
+ .oo-ui-box-sizing(border-box);
+
+ .oo-ui-indicatorElement-indicator,
+ .oo-ui-iconElement-icon {
+ position: absolute;
+ background-position: center center;
+ background-repeat: no-repeat;
+ }
+ }
+
+ > .oo-ui-menuSelectWidget {
+ z-index: 1;
+ width: 100%;
+ }
+
+ &.oo-ui-widget-disabled .oo-ui-dropdownWidget-handle {
+ cursor: default;
+ }
+
+ .theme-oo-ui-dropdownWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/IconWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/IconWidget.less
new file mode 100644
index 00000000..03f6ab79
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/IconWidget.less
@@ -0,0 +1,10 @@
+@import '../common';
+
+.oo-ui-iconWidget {
+ display: inline-block;
+ vertical-align: middle;
+ background-position: center center;
+ background-repeat: no-repeat;
+
+ .theme-oo-ui-iconWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/IndicatorWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/IndicatorWidget.less
new file mode 100644
index 00000000..c1b00458
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/IndicatorWidget.less
@@ -0,0 +1,10 @@
+@import '../common';
+
+.oo-ui-indicatorWidget {
+ display: inline-block;
+ vertical-align: middle;
+ background-position: center center;
+ background-repeat: no-repeat;
+
+ .theme-oo-ui-indicatorWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/InputWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/InputWidget.less
new file mode 100644
index 00000000..93628830
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/InputWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-inputWidget {
+ .theme-oo-ui-inputWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/LabelWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/LabelWidget.less
new file mode 100644
index 00000000..cddd1a0a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/LabelWidget.less
@@ -0,0 +1,7 @@
+@import '../common';
+
+.oo-ui-labelWidget {
+ display: inline-block;
+
+ .theme-oo-ui-labelWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/MenuOptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/MenuOptionWidget.less
new file mode 100644
index 00000000..f7f9f7a4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/MenuOptionWidget.less
@@ -0,0 +1,21 @@
+@import '../common';
+
+.oo-ui-menuOptionWidget {
+ position: relative;
+
+ .oo-ui-iconElement-icon {
+ display: none;
+ }
+
+ &.oo-ui-optionWidget {
+ &-selected {
+ background-color: transparent;
+
+ .oo-ui-iconElement-icon {
+ display: block;
+ }
+ }
+ }
+
+ .theme-oo-ui-menuOptionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/MenuSectionOptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/MenuSectionOptionWidget.less
new file mode 100644
index 00000000..f670f7f5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/MenuSectionOptionWidget.less
@@ -0,0 +1,8 @@
+@import '../common';
+
+.oo-ui-menuSectionOptionWidget {
+ cursor: default;
+
+ .theme-oo-ui-menuSectionOptionWidget();
+}
+
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/MenuSelectWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/MenuSelectWidget.less
new file mode 100644
index 00000000..0585469f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/MenuSelectWidget.less
@@ -0,0 +1,15 @@
+@import '../common';
+
+.oo-ui-menuSelectWidget {
+ position: absolute;
+
+ input {
+ position: absolute;
+ width: 0;
+ height: 0;
+ overflow: hidden;
+ opacity: 0;
+ }
+
+ .theme-oo-ui-menuSelectWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/OptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/OptionWidget.less
new file mode 100644
index 00000000..8543fc74
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/OptionWidget.less
@@ -0,0 +1,20 @@
+@import '../common';
+
+.oo-ui-optionWidget {
+ position: relative;
+ display: block;
+ cursor: pointer;
+
+ &.oo-ui-widget-disabled {
+ cursor: default;
+ }
+
+ &.oo-ui-labelElement .oo-ui-labelElement-label {
+ display: block;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ }
+
+ .theme-oo-ui-optionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/OutlineControlsWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/OutlineControlsWidget.less
new file mode 100644
index 00000000..97d4e1d7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/OutlineControlsWidget.less
@@ -0,0 +1,32 @@
+@import '../common';
+
+.oo-ui-outlineControlsWidget {
+ &-items,
+ &-movers {
+ float: left;
+ .oo-ui-box-sizing(border-box);
+ }
+
+ > .oo-ui-iconElement-icon {
+ float: left;
+ background-position: right center;
+ background-repeat: no-repeat;
+ }
+
+ &-items {
+ float: left;
+
+ .oo-ui-buttonWidget {
+ float: left;
+ }
+ }
+ &-movers {
+ float: right;
+
+ .oo-ui-buttonWidget {
+ float: right;
+ }
+ }
+
+ .theme-oo-ui-outlineControlsWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/OutlineOptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/OutlineOptionWidget.less
new file mode 100644
index 00000000..6558c652
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/OutlineOptionWidget.less
@@ -0,0 +1,9 @@
+@import '../common';
+
+.oo-ui-outlineOptionWidget {
+ position: relative;
+ cursor: pointer;
+ .oo-ui-unselectable();
+
+ .theme-oo-ui-outlineOptionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/OutlineSelectWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/OutlineSelectWidget.less
new file mode 100644
index 00000000..65fc621b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/OutlineSelectWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-outlineSelectWidget {
+ .theme-oo-ui-outlineSelectWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/PopupButtonWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/PopupButtonWidget.less
new file mode 100644
index 00000000..805239ed
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/PopupButtonWidget.less
@@ -0,0 +1,12 @@
+@import '../common';
+
+.oo-ui-popupButtonWidget {
+ position: relative;
+
+ .oo-ui-popupWidget {
+ position: absolute;
+ cursor: auto;
+ }
+
+ .theme-oo-ui-popupButtonWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/PopupWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/PopupWidget.less
new file mode 100644
index 00000000..d8fdd40c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/PopupWidget.less
@@ -0,0 +1,49 @@
+@import '../common';
+
+.oo-ui-popupWidget {
+ position: absolute;
+ /* @noflip */
+ left: 0;
+
+ &-popup {
+ position: relative;
+ overflow: hidden;
+ z-index: 1;
+ }
+
+ &-anchor {
+ display: none;
+ z-index: 1;
+ }
+
+ &-anchored {
+ .oo-ui-popupWidget-anchor {
+ display: block;
+ position: absolute;
+ top: 0;
+ /* @noflip */
+ left: 0;
+ background-repeat: no-repeat;
+ }
+ }
+
+ &-head {
+ .oo-ui-unselectable();
+
+ .oo-ui-buttonWidget {
+ float: right;
+ }
+
+ .oo-ui-labelElement-label {
+ float: left;
+ cursor: default;
+ }
+ }
+
+ &-body {
+ clear: both;
+ overflow: hidden;
+ }
+
+ .theme-oo-ui-popupWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ProgressBarWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ProgressBarWidget.less
new file mode 100644
index 00000000..82993fc1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ProgressBarWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-progressBarWidget {
+ .theme-oo-ui-progressBarWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/RadioInputWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/RadioInputWidget.less
new file mode 100644
index 00000000..7b75f871
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/RadioInputWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-radioInputWidget {
+ .theme-oo-ui-radioInputWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/RadioOptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/RadioOptionWidget.less
new file mode 100644
index 00000000..9a83997a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/RadioOptionWidget.less
@@ -0,0 +1,13 @@
+@import '../common';
+
+.oo-ui-radioOptionWidget {
+ cursor: default;
+
+ .oo-ui-radioInputWidget,
+ &.oo-ui-labelElement .oo-ui-labelElement-label {
+ display: inline-block;
+ vertical-align: middle;
+ }
+
+ .theme-oo-ui-radioOptionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/RadioSelectWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/RadioSelectWidget.less
new file mode 100644
index 00000000..f6900cb6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/RadioSelectWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-radioSelectWidget {
+ .theme-oo-ui-radioSelectWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/SearchWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/SearchWidget.less
new file mode 100644
index 00000000..920beacf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/SearchWidget.less
@@ -0,0 +1,25 @@
+@import '../common';
+
+.oo-ui-searchWidget {
+ &-query {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+
+ .oo-ui-textInputWidget {
+ width: 100%;
+ }
+ }
+
+ &-results {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ overflow-x: hidden;
+ overflow-y: auto;
+ }
+
+ .theme-oo-ui-searchWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/SelectWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/SelectWidget.less
new file mode 100644
index 00000000..2ad0b943
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/SelectWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-selectWidget {
+ .theme-oo-ui-selectWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/TabOptionWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/TabOptionWidget.less
new file mode 100644
index 00000000..0b83154c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/TabOptionWidget.less
@@ -0,0 +1,8 @@
+@import '../common';
+
+.oo-ui-tabOptionWidget {
+ display: inline-block;
+ vertical-align: bottom;
+
+ .theme-oo-ui-tabOptionWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/TabSelectWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/TabSelectWidget.less
new file mode 100644
index 00000000..b3a57c98
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/TabSelectWidget.less
@@ -0,0 +1,9 @@
+@import '../common';
+
+.oo-ui-tabSelectWidget {
+ text-align: left;
+ white-space: nowrap;
+ overflow: hidden;
+
+ .theme-oo-ui-tabSelectWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/TextInputMenuSelectWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/TextInputMenuSelectWidget.less
new file mode 100644
index 00000000..6edc474b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/TextInputMenuSelectWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-textInputMenuSelectWidget {
+ .theme-oo-ui-textInputMenuSelectWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/TextInputWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/TextInputWidget.less
new file mode 100644
index 00000000..dd0cdf46
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/TextInputWidget.less
@@ -0,0 +1,71 @@
+@import '../common';
+
+.oo-ui-textInputWidget {
+ position: relative;
+ // Necessary for proper alignment when used with display: inline-block
+ vertical-align: middle;
+ .oo-ui-box-sizing(border-box);
+
+ input,
+ textarea {
+ display: inline-block;
+ width: 100%;
+ resize: none;
+ .oo-ui-box-sizing(border-box);
+ }
+
+ > .oo-ui-iconElement-icon,
+ > .oo-ui-indicatorElement-indicator,
+ > .oo-ui-labelElement-label {
+ display: none;
+ }
+
+ &.oo-ui-iconElement > .oo-ui-iconElement-icon,
+ &.oo-ui-indicatorElement > .oo-ui-indicatorElement-indicator {
+ display: block;
+ position: absolute;
+ top: 0;
+ height: 100%;
+ background-repeat: no-repeat;
+
+ .oo-ui-unselectable();
+ }
+
+ &.oo-ui-widget-enabled {
+ > .oo-ui-iconElement-icon,
+ > .oo-ui-indicatorElement-indicator {
+ cursor: pointer;
+ }
+ }
+
+ &.oo-ui-labelElement > .oo-ui-labelElement-label {
+ display: block;
+ }
+
+ > .oo-ui-iconElement-icon {
+ left: 0;
+ }
+
+ > .oo-ui-indicatorElement-indicator {
+ right: 0;
+ }
+
+ > .oo-ui-labelElement-label {
+ position: absolute;
+ top: 0;
+ }
+
+ &-labelPosition-after {
+ > .oo-ui-labelElement-label {
+ right: 0
+ }
+ }
+
+ &-labelPosition-before {
+ > .oo-ui-labelElement-label {
+ left: 0
+ }
+ }
+
+ .theme-oo-ui-textInputWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ToggleButtonWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ToggleButtonWidget.less
new file mode 100644
index 00000000..5441e4e8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ToggleButtonWidget.less
@@ -0,0 +1,8 @@
+@import '../common';
+
+.oo-ui-toggleButtonWidget {
+ display: inline-block;
+ vertical-align: middle;
+
+ .theme-oo-ui-toggleButtonWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ToggleSwitchWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ToggleSwitchWidget.less
new file mode 100644
index 00000000..f030cf66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ToggleSwitchWidget.less
@@ -0,0 +1,41 @@
+@import '../common';
+
+.oo-ui-toggleSwitchWidget {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+ overflow: hidden;
+ cursor: pointer;
+
+ .oo-ui-box-sizing(border-box);
+ .oo-ui-transform(translateZ(0px));
+
+ &.oo-ui-widget-disabled {
+ cursor: default;
+ }
+
+ &-grip {
+ position: absolute;
+ display: block;
+
+ .oo-ui-box-sizing(border-box);
+ }
+
+ .oo-ui-toggleSwitchWidget-glow {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ right: 0;
+ left: 0;
+
+ .oo-ui-unselectable();
+ }
+
+ .oo-ui-toggleWidget-off & {
+ &-glow {
+ display: none;
+ }
+ }
+
+ .theme-oo-ui-toggleSwitchWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/styles/widgets/ToggleWidget.less b/vendor/oojs/oojs-ui/src/styles/widgets/ToggleWidget.less
new file mode 100644
index 00000000..f51e45b5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/styles/widgets/ToggleWidget.less
@@ -0,0 +1,5 @@
+@import '../common';
+
+.oo-ui-toggleWidget {
+ .theme-oo-ui-toggleWidget();
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/ApexTheme.js b/vendor/oojs/oojs-ui/src/themes/apex/ApexTheme.js
new file mode 100644
index 00000000..157dfb64
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/ApexTheme.js
@@ -0,0 +1,18 @@
+/**
+ * @class
+ * @extends OO.ui.Theme
+ *
+ * @constructor
+ */
+OO.ui.ApexTheme = function OoUiApexTheme() {
+ // Parent constructor
+ OO.ui.ApexTheme.super.call( this );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ApexTheme, OO.ui.Theme );
+
+/* Instantiation */
+
+OO.ui.theme = new OO.ui.ApexTheme();
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/common.less b/vendor/oojs/oojs-ui/src/themes/apex/common.less
new file mode 100644
index 00000000..74dd3a3b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/common.less
@@ -0,0 +1,27 @@
+// Base variables and mixins
+@import '../../styles/common';
+
+// Theme variables
+
+@progressive: #087ecc;
+@constructive: #76ab36;
+@destructive: #d45353;
+
+@progressive-gradient-start: #eaf4fa;
+@progressive-gradient-end: #b0d9ee;
+@progressive-border: #a6cee1;
+@progressive-border-selected: #9dc2d4;
+
+@constructive-gradient-start: #f0fbe1;
+@constructive-gradient-end: #c3e59a;
+@constructive-border: #b8d892;
+@constructive-border-selected: #adcb89;
+
+@oo-ui-default-image-path: 'themes/apex/images';
+
+@icon-size: unit(24 / 16 / 0.8, em);
+@indicator-size: unit(12 / 16 / 0.8, em);
+
+// Theme mixins
+
+// (add mixins here)
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/core.less b/vendor/oojs/oojs-ui/src/themes/apex/core.less
new file mode 100644
index 00000000..1c83a946
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/core.less
@@ -0,0 +1,12 @@
+// Base and theme variables and mixins
+@import 'common';
+
+// Theme rules
+@import 'elements';
+@import 'layouts';
+@import 'tools';
+@import 'widgets';
+@import 'windows';
+
+// Base rules
+@import '../../styles/core';
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/elements.less b/vendor/oojs/oojs-ui/src/themes/apex/elements.less
new file mode 100644
index 00000000..bfd11228
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/elements.less
@@ -0,0 +1,247 @@
+@import 'common';
+
+.theme-oo-ui-element () {}
+
+.theme-oo-ui-buttonElement () {
+ > .oo-ui-buttonElement-button {
+ color: #333;
+ }
+
+ &.oo-ui-iconElement > .oo-ui-buttonElement-button {
+ > .oo-ui-iconElement-icon {
+ margin-left: 0;
+ }
+ }
+
+ &.oo-ui-indicatorElement > .oo-ui-buttonElement-button {
+ > .oo-ui-indicatorElement-indicator {
+ width: @indicator-size;
+ height: @indicator-size;
+ margin: @indicator-size / 2;
+ }
+ }
+
+ &.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ margin-left: @indicator-size / 2;
+ }
+ &.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ width: @icon-size;
+ height: @icon-size;
+ }
+
+ &-frameless {
+ > .oo-ui-buttonElement-button {
+ > .oo-ui-iconElement-icon {
+ /* Don't animate opacities for now, causes wiggling in Chrome (bug 63020) */
+ /*.oo-ui-transition(opacity 200ms);*/
+ }
+
+ &:hover,
+ &:focus {
+ outline: none;
+
+ > .oo-ui-iconElement-icon {
+ opacity: 1;
+ }
+ > .oo-ui-labelElement-label {
+ color: #000;
+ }
+ }
+
+ > .oo-ui-labelElement-label {
+ color: #333;
+ }
+ }
+
+ &.oo-ui-labelElement {
+ > .oo-ui-buttonElement-button {
+ > .oo-ui-labelElement-label {
+ margin-left: 0.25em;
+ }
+ }
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ color: @progressive;
+ }
+
+ &-constructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ color: @constructive;
+ }
+
+ &-destructive > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ color: @destructive;
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
+ > .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ > .oo-ui-labelElement-label {
+ color: #ccc;
+ }
+ }
+ }
+
+ &-framed {
+ > .oo-ui-buttonElement-button {
+ margin: 0.1em 0;
+ padding: 0.2em 0.8em;
+ border-radius: 0.3em;
+ text-shadow: 0 1px 1px rgba(255, 255, 255, 0.5);
+ border: 1px #c9c9c9 solid;
+ .oo-ui-transition(border-color 100ms ease-in-out);
+ .oo-ui-vertical-gradient(#fff, #ddd);
+
+ &:hover,
+ &:focus {
+ border-color: #aaa;
+ outline: none;
+ }
+ }
+
+ // Support <input/> from ButtonInputWidget
+ > input.oo-ui-buttonElement-button,
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ line-height: @icon-size;
+ }
+
+ &.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
+ color: black;
+ border-color: #c9c9c9;
+ .oo-ui-vertical-gradient(#ddd, #fff);
+ }
+
+ &.oo-ui-iconElement {
+ > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ margin-left: -0.5em;
+ margin-right: -0.5em;
+ }
+
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ margin-right: 0.3em;
+ }
+ }
+
+ &.oo-ui-indicatorElement {
+ > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ /* -0.5 - 0.475 */
+ margin-left: -0.005em;
+ margin-right: -0.005em;
+ }
+
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator,
+ &.oo-ui-iconElement:not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ margin-left: @indicator-size / 2;
+ margin-right: -0.275em;
+ }
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive {
+ > .oo-ui-buttonElement-button {
+ border: 1px solid @progressive-border;
+ .oo-ui-vertical-gradient(@progressive-gradient-start, @progressive-gradient-end);
+
+ &:hover,
+ &:focus {
+ border-color: @progressive-border-selected;
+ }
+ }
+
+ &.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ border: 1px solid @progressive-border;
+ .oo-ui-vertical-gradient(@progressive-gradient-end, @progressive-gradient-start);
+ }
+ }
+
+ &-constructive {
+ > .oo-ui-buttonElement-button {
+ border: 1px solid @constructive-border;
+ .oo-ui-vertical-gradient(@constructive-gradient-start, @constructive-gradient-end);
+
+ &:hover,
+ &:focus {
+ border-color: @constructive-border-selected;
+ }
+ }
+
+ &.oo-ui-widget-enabled > .oo-ui-buttonElement-button:active,
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ border: 1px solid @constructive-border;
+ .oo-ui-vertical-gradient(@constructive-gradient-end, @constructive-gradient-start);
+ }
+ }
+
+ &-destructive > .oo-ui-buttonElement-button {
+ color: @destructive;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ > .oo-ui-buttonElement-button,
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ opacity: 0.5;
+ // Opacity causes 1px measurement errors in Chrome, so force GPU rendering
+ .oo-ui-force-webkit-gpu();
+ box-shadow: none;
+ color: #333;
+ background: #eee;
+ border-color: #ccc;
+
+ &:hover,
+ &:focus {
+ border-color: #ccc;
+ box-shadow: none;
+ }
+ }
+ }
+ }
+}
+
+.theme-oo-ui-clippableElement () {}
+
+.theme-oo-ui-flaggedElement () {}
+
+.theme-oo-ui-draggableElement () {}
+
+.theme-oo-ui-groupElement () {}
+
+.theme-oo-ui-draggableGroupElement () {}
+
+.theme-oo-ui-iconElement () {
+ .oo-ui-iconElement-icon,
+ &.oo-ui-iconElement-icon {
+ opacity: 0.8;
+ background-size: contain;
+ background-position: center center;
+ }
+}
+
+.theme-oo-ui-indicatorElement () {
+ .oo-ui-indicatorElement-indicator,
+ &.oo-ui-indicatorElement-indicator {
+ opacity: 0.8;
+ background-size: contain;
+ background-position: center center;
+ }
+}
+
+.theme-oo-ui-labelElement () {}
+
+.theme-oo-ui-lookupElement () {}
+
+.theme-oo-ui-popupElement () {}
+
+.theme-oo-ui-tabIndexedElement () {}
+
+.theme-oo-ui-titledElement () {}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-advanced.json b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-advanced.json
new file mode 100644
index 00000000..8fdc5051
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-advanced.json
@@ -0,0 +1,75 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "alignCentre": { "file": "images/icons/align-center.svg" },
+ "alignLeft": { "file": "images/icons/align-float-left.svg" },
+ "alignRight": { "file": "images/icons/align-float-right.svg" },
+ "find": { "file": {
+ "ltr": "images/icons/find-ltr.svg",
+ "rtl": "images/icons/find-rtl.svg"
+ } },
+ "insert": { "file": "images/icons/insert.svg" },
+ "layout": { "file": {
+ "ltr": "images/icons/layout-ltr.svg",
+ "rtl": "images/icons/layout-rtl.svg"
+ } },
+ "newline": { "file": {
+ "ltr": "images/icons/newline-ltr.svg",
+ "rtl": "images/icons/newline-rtl.svg"
+ } },
+ "redirect": { "file": {
+ "ltr": "images/icons/redirect-ltr.svg",
+ "rtl": "images/icons/redirect-rtl.svg"
+ } },
+ "noWikiText": { "file": {
+ "ltr": "images/icons/noWikiText-ltr.svg",
+ "rtl": "images/icons/noWikiText-rtl.svg"
+ } },
+ "outline": { "file": {
+ "ltr": "images/icons/outline-ltr.svg",
+ "rtl": "images/icons/outline-rtl.svg"
+ } },
+ "puzzle": { "file": {
+ "ltr": "images/icons/puzzle-ltr.svg",
+ "rtl": "images/icons/puzzle-rtl.svg"
+ } },
+ "quotes": { "file": {
+ "ltr": "images/icons/quotes-ltr.svg",
+ "rtl": "images/icons/quotes-rtl.svg"
+ } },
+ "quotesAdd": { "file": {
+ "ltr": "images/icons/quotesAdd-ltr.svg",
+ "rtl": "images/icons/quotesAdd-rtl.svg"
+ } },
+ "redirect": { "file": {
+ "ltr": "images/icons/redirect-ltr.svg",
+ "rtl": "images/icons/redirect-rtl.svg"
+ } },
+ "searchCaseSensitive": { "file": "images/icons/case-sensitive.svg" },
+ "searchRegularExpression": { "file": "images/icons/regular-expression.svg" },
+ "specialCharacter": { "file": "images/icons/specialCharacter.svg" },
+ "table": { "file": "images/icons/table.svg" },
+ "tableAddColumnAfter": { "file": {
+ "ltr": "images/icons/table-insert-column-rtl.svg",
+ "rtl": "images/icons/table-insert-column-ltr.svg"
+ } },
+ "tableAddColumnBefore": { "file": {
+ "ltr": "images/icons/table-insert-column-ltr.svg",
+ "rtl": "images/icons/table-insert-column-rtl.svg"
+ } },
+ "tableAddRowAfter": { "file": "images/icons/table-insert-row-after.svg" },
+ "tableAddRowBefore": { "file": "images/icons/table-insert-row-before.svg" },
+ "tableCaption": { "file": "images/icons/table-caption.svg" },
+ "tableMergeCells": { "file": "images/icons/table-merge-cells.svg" },
+ "templateAdd": { "file": {
+ "ltr": "images/icons/templateAdd-ltr.svg",
+ "rtl": "images/icons/templateAdd-rtl.svg"
+ } },
+ "translation": { "file": {
+ "ltr": "images/icons/translation-ltr.svg",
+ "rtl": "images/icons/translation-rtl.svg"
+ } },
+ "wikiText": { "file": "images/icons/wikiText.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-core.json b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-core.json
new file mode 100644
index 00000000..95e8358e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-core.json
@@ -0,0 +1,24 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "edit": { "file": {
+ "ltr": "images/icons/edit-ltr.svg",
+ "rtl": "images/icons/edit-rtl.svg"
+ } },
+ "editLock": { "file": {
+ "ltr": "images/icons/editLock-ltr.svg",
+ "rtl": "images/icons/editLock-rtl.svg"
+ } },
+ "editUndo": { "file": {
+ "ltr": "images/icons/editUndo-ltr.svg",
+ "rtl": "images/icons/editUndo-rtl.svg"
+ } },
+ "link": { "file": "images/icons/link.svg" },
+ "linkExternal": { "file": {
+ "ltr": "images/icons/external-link-ltr.svg",
+ "rtl": "images/icons/external-link-rtl.svg"
+ } },
+ "linkSecure": { "file": "images/icons/secure-link.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-list.json b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-list.json
new file mode 100644
index 00000000..490f8faf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-list.json
@@ -0,0 +1,22 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "indent": { "file": {
+ "ltr": "images/icons/indent-ltr.svg",
+ "rtl": "images/icons/indent-rtl.svg"
+ } },
+ "listBullet": { "file": {
+ "ltr": "images/icons/listBullet-ltr.svg",
+ "rtl": "images/icons/listBullet-rtl.svg"
+ } },
+ "listNumbered": { "file": {
+ "ltr": "images/icons/listNumbered-ltr.svg",
+ "rtl": "images/icons/listNumbered-rtl.svg"
+ } },
+ "outdent": { "file": {
+ "ltr": "images/icons/outdent-ltr.svg",
+ "rtl": "images/icons/outdent-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-styling.json b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-styling.json
new file mode 100644
index 00000000..65fbc218
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/icons-editing-styling.json
@@ -0,0 +1,72 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "bigger": { "file": {
+ "ltr": "images/icons/bigger-ltr.svg",
+ "rtl": "images/icons/bigger-rtl.svg"
+ } },
+ "smaller": { "file": {
+ "ltr": "images/icons/smaller-ltr.svg",
+ "rtl": "images/icons/smaller-rtl.svg"
+ } },
+ "subscript": { "file": {
+ "ltr": "images/icons/subscript-ltr.svg",
+ "rtl": "images/icons/subscript-rtl.svg"
+ } },
+ "superscript": { "file": {
+ "ltr": "images/icons/superscript-ltr.svg",
+ "rtl": "images/icons/superscript-rtl.svg"
+ } },
+ "bold": { "file": {
+ "default": "images/icons/bold-a.svg",
+ "lang": {
+ "ar": "images/icons/bold-arab-ain.svg",
+ "be": "images/icons/bold-cyrl-te.svg",
+ "cs,en,he,ml,pl": "images/icons/bold-b.svg",
+ "da,de,hu,ksh,nn,no,sv": "images/icons/bold-f.svg",
+ "es,gl,pt": "images/icons/bold-n.svg",
+ "eu,fi": "images/icons/bold-l.svg",
+ "fa": "images/icons/bold-arab-dad.svg",
+ "fr,it": "images/icons/bold-g.svg",
+ "hy": "images/icons/bold-armn-to.svg",
+ "ka": "images/icons/bold-geor-man.svg",
+ "ky,ru": "images/icons/bold-cyrl-zhe.svg",
+ "nl": "images/icons/bold-v.svg",
+ "os": "images/icons/bold-cyrl-be.svg"
+ }
+ } },
+ "italic": { "file": {
+ "default": "images/icons/italic-a.svg",
+ "lang": {
+ "ar": "images/icons/italic-arab-meem.svg",
+ "cs,en,fr,he,ml,pl,pt": "images/icons/italic-i.svg",
+ "be,da,de,fi,ky,nn,no,os,sv,ru": "images/icons/italic-k.svg",
+ "es,gl,it,nl": "images/icons/italic-c.svg",
+ "eu": "images/icons/italic-e.svg",
+ "fa": "images/icons/italic-arab-keheh-jeem.svg",
+ "hu": "images/icons/italic-d.svg",
+ "hy": "images/icons/italic-armn-sha.svg",
+ "ksh": "images/icons/italic-s.svg",
+ "ka": "images/icons/italic-geor-kan.svg"
+ }
+ } },
+ "strikethrough": { "file": {
+ "default": "images/icons/strikethrough-a.svg",
+ "lang": {
+ "en": "images/icons/strikethrough-s.svg",
+ "fi": "images/icons/strikethrough-y.svg"
+ }
+ } },
+ "underline": { "file": {
+ "default": "images/icons/underline-a.svg",
+ "lang": {
+ "en": "images/icons/underline-u.svg"
+ }
+ } },
+ "textLanguage": { "file": "images/icons/language.svg" },
+ "textDirLTR": { "file": "images/icons/text-dir-lefttoright.svg" },
+ "textDirRTL": { "file": "images/icons/text-dir-righttoleft.svg" },
+ "textStyle": { "file": "images/icons/text-style.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/icons-moderation.json b/vendor/oojs/oojs-ui/src/themes/apex/icons-moderation.json
new file mode 100644
index 00000000..f904cc26
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/icons-moderation.json
@@ -0,0 +1,33 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "block": { "file": "images/icons/block.svg" },
+ "blockUndo": { "file": {
+ "ltr": "images/icons/blockUndo-ltr.svg",
+ "rtl": "images/icons/blockUndo-rtl.svg"
+ } },
+ "flag": { "file": {
+ "ltr": "images/icons/flag-ltr.svg",
+ "rtl": "images/icons/flag-rtl.svg"
+ } },
+ "flagUndo": { "file": {
+ "ltr": "images/icons/flagUndo-ltr.svg",
+ "rtl": "images/icons/flagUndo-rtl.svg"
+ } },
+ "lock": { "file": "images/icons/lock.svg" },
+ "star": { "file": "images/icons/star.svg" },
+ "trash": { "file": "images/icons/trash.svg" },
+ "trashUndo": { "file": {
+ "ltr": "images/icons/trashUndo-ltr.svg",
+ "rtl": "images/icons/trashUndo-rtl.svg"
+ } },
+ "unLock": { "file": {
+ "ltr": "images/icons/unLock-ltr.svg",
+ "rtl": "images/icons/unLock-rtl.svg"
+ } },
+ "unStar": { "file": "images/icons/unStar.svg" }
+
+
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/icons-movement.json b/vendor/oojs/oojs-ui/src/themes/apex/icons-movement.json
new file mode 100644
index 00000000..9aa1b809
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/icons-movement.json
@@ -0,0 +1,27 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "arrowNext": { "file": {
+ "ltr": "images/icons/arrow-ltr.svg",
+ "rtl": "images/icons/arrow-rtl.svg"
+ } },
+ "arrowLast": { "file": {
+ "ltr": "images/icons/arrow-rtl.svg",
+ "rtl": "images/icons/arrow-ltr.svg"
+ } },
+ "caretNext": { "file": {
+ "ltr": "images/icons/caret-rtl.svg",
+ "rtl": "images/icons/caret-ltr.svg"
+ } },
+ "caretLast": { "file": {
+ "ltr": "images/icons/caret-ltr.svg",
+ "rtl": "images/icons/caret-rtl.svg"
+ } },
+ "caretDown": { "file": "images/icons/caretDown.svg" },
+ "caretUp": { "file": "images/icons/caretUp.svg" },
+ "downTriangle": { "file": "images/icons/downTriangle.svg" },
+ "move": { "file": "images/icons/move.svg" },
+ "upTriangle": { "file": "images/icons/upTriangle.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/icons.json b/vendor/oojs/oojs-ui/src/themes/apex/icons.json
new file mode 100644
index 00000000..9372363f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/icons.json
@@ -0,0 +1,50 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "circle": { "file": "images/icons/circle.svg" },
+ "add": { "file": "images/icons/add.svg" },
+ "advanced": { "file": "images/icons/advanced.svg" },
+ "cancel": { "file": "images/icons/cancel.svg" },
+ "alert": { "file": "images/icons/alert.svg" },
+ "check": { "file": "images/icons/check.svg" },
+ "close": { "file": "images/icons/close.svg" },
+ "code": { "file": "images/icons/code.svg" },
+ "collapse": { "file": "images/icons/collapse.svg" },
+ "comment": { "file": "images/icons/comment.svg" },
+ "ellipsis": { "file": "images/icons/ellipsis.svg" },
+ "expand": { "file": "images/icons/expand.svg" },
+ "help": { "file": {
+ "ltr": "images/icons/help-ltr.svg",
+ "rtl": "images/icons/help-rtl.svg",
+ "lang": {
+ "he,yi": "images/icons/help-ltr.svg"
+ }
+ } },
+ "history": { "file": "images/icons/history.svg" },
+ "info": { "file": "images/icons/info.svg" },
+ "menu": { "file": "images/icons/menu.svg" },
+ "next": { "file": {
+ "ltr": "images/icons/move-ltr.svg",
+ "rtl": "images/icons/move-rtl.svg"
+ } },
+ "picture": { "file": "images/icons/picture.svg" },
+ "previous": { "file": {
+ "ltr": "images/icons/move-rtl.svg",
+ "rtl": "images/icons/move-ltr.svg"
+ } },
+ "redo": { "file": {
+ "ltr": "images/icons/arched-arrow-ltr.svg",
+ "rtl": "images/icons/arched-arrow-rtl.svg"
+ } },
+ "remove": { "file": "images/icons/remove.svg" },
+ "search": { "file": "images/icons/search.svg" },
+ "settings": { "file": "images/icons/settings.svg" },
+ "tag": { "file": "images/icons/tag.svg" },
+ "undo": { "file": {
+ "ltr": "images/icons/arched-arrow-rtl.svg",
+ "rtl": "images/icons/arched-arrow-ltr.svg"
+ } },
+ "window": { "file": "images/icons/window.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/add.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/add.svg
new file mode 100644
index 00000000..29e5dba8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/add.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="add">
+ <path id="plus" d="M13 8h-2v3h-3v2h3v3h2v-3h3v-2h-3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/advanced.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/advanced.svg
new file mode 100644
index 00000000..201b4d73
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/advanced.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="settings">
+ <path id="gear" d="M20.869 13.476c.079-.482.131-.972.131-1.476s-.052-.994-.131-1.476l-2.463-.259c-.149-.556-.367-1.082-.648-1.57l1.558-1.924c-.576-.806-1.281-1.511-2.087-2.087l-1.924 1.558c-.488-.281-1.015-.499-1.57-.648l-.259-2.463c-.482-.079-.972-.131-1.476-.131s-.994.052-1.476.131l-.259 2.463c-.555.149-1.081.367-1.57.648l-1.924-1.557c-.805.576-1.51 1.281-2.086 2.086l1.558 1.924c-.281.488-.499 1.015-.648 1.57l-2.463.259c-.08.482-.132.972-.132 1.476s.052.994.131 1.476l2.463.259c.149.556.367 1.082.648 1.57l-1.558 1.924c.576.806 1.281 1.511 2.087 2.087l1.924-1.558c.488.281 1.015.499 1.57.648l.259 2.463c.482.079.972.131 1.476.131s.994-.052 1.476-.131l.259-2.463c.556-.149 1.082-.367 1.57-.648l1.924 1.558c.806-.576 1.511-1.281 2.087-2.087l-1.558-1.924c.281-.488.499-1.015.648-1.57l2.463-.259zm-8.869 2.522c-2.209 0-3.998-1.789-3.998-3.998s1.789-3.998 3.998-3.998 3.998 1.789 3.998 3.998-1.789 3.998-3.998 3.998z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/alert.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/alert.svg
new file mode 100644
index 00000000..f0c65224
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/alert.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="alert">
+ <path id="point" d="M11 16h2v2h-2z"/>
+ <path id="stroke" d="M13.516 10h-3l.484 5h2z"/>
+ <path id="triangle" d="M12.017 5.974l7.519 13.026h-15.04l7.521-13.026m0-2.474c-.544 0-1.088.357-1.5 1.071l-7.985 13.831c-.825 1.429-.15 2.598 1.5 2.598h15.968c1.65 0 2.325-1.169 1.5-2.599l-7.983-13.829c-.413-.715-.956-1.072-1.5-1.072z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-center.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-center.svg
new file mode 100644
index 00000000..887c2f66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-center.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="align-center">
+ <path d="M9 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1h-6c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zM3.5 18h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM3.5 6h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-left.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-left.svg
new file mode 100644
index 00000000..ce9761e2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-left.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="align-float-left">
+ <path d="M4 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1h-6c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zM13.5 9h7c.277 0 .5.223.5.5s-.223.5-.5.5h-7c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM13.5 12h7c.277 0 .5.223.5.5s-.223.5-.5.5h-7c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM13.5 15h7c.277 0 .5.223.5.5s-.223.5-.5.5h-7c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM3.5 6h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM3.5 18h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-right.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-right.svg
new file mode 100644
index 00000000..557692ae
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/align-float-right.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="align-float-right">
+ <path d="M20 9h-6c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h6c.554 0 1-.446 1-1v-5c0-.554-.446-1-1-1zM10.5 9h-7c-.277 0-.5.223-.5.5s.223.5.5.5h7c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM10.5 12h-7c-.277 0-.5.223-.5.5s.223.5.5.5h7c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM10.5 15h-7c-.277 0-.5.223-.5.5s.223.5.5.5h7c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM20.5 6h-17c-.277 0-.5.223-.5.5s.223.5.5.5h17c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM20.5 18h-17c-.277 0-.5.223-.5.5s.223.5.5.5h17c.277 0 .5-.223.5-.5s-.223-.5-.5-.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-ltr.svg
new file mode 100644
index 00000000..8a670ef2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="arched-arrow-ltr">
+ <path id="arrow" d="M19.925 14.937l-2.391-6.901-1.48 2.329c-.964-.845-2.699-1.85-5.513-1.823-4.887.046-6.524 4.244-6.524 4.244s2.753-2.639 6.925-1.949c1.729.286 3.007 1.206 3.675 1.791l-1.474 2.319 6.782-.01z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-rtl.svg
new file mode 100644
index 00000000..01fc216b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arched-arrow-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="arched-arrow-rtl">
+ <path id="arrow" d="M13.401 8.542c-2.814-.027-4.549.978-5.513 1.823l-1.48-2.329-2.391 6.901 6.782.009-1.474-2.319c.668-.584 1.945-1.504 3.675-1.791 4.172-.69 6.925 1.949 6.925 1.949s-1.637-4.197-6.524-4.243z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-ltr.svg
new file mode 100644
index 00000000..b07621e8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M16 12h-10c-1.7 0-3 1.3-3 3h13v3l5-4.5-5-4.5v3z" id="path6"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-rtl.svg
new file mode 100644
index 00000000..a0189283
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/arrow-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M8 12h10c1.7 0 3 1.3 3 3h-13v3l-5-4.5 5-4.5v3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-ltr.svg
new file mode 100644
index 00000000..94ec6704
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12.666 6h-1.372l-4.48 12h1.705l1.494-4h3.999l1.508 4h1.666l-4.52-12zm-2.28 7l1.617-4.333 1.634 4.333h-3.251z" id="a"/>
+ <g id="up">
+ <path id="arrow" d="M15.5 9h7l-3.5-6z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-rtl.svg
new file mode 100644
index 00000000..b2a6c139
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bigger-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
+ <g id="up">
+ <path id="arrow" d="M1.5 9h7L5 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/block.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/block.svg
new file mode 100644
index 00000000..0ddd1d47
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/block.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9h-10v-2h10v2z" id="path4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-ltr.svg
new file mode 100644
index 00000000..3d9cfd7d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g66">
+ <path d="M17 11v2h-2l3.6 3.6c.9-1.3 1.4-2.9 1.4-4.6 0-4.4-3.6-8-8-8-1.7 0-3.3.5-4.6 1.4l5.6 5.6h4zm-13-7l-1 1 2.4 2.4c-.9 1.3-1.4 2.9-1.4 4.6 0 4.4 3.6 8 8 8 1.7 0 3.3-.5 4.6-1.4l2.4 2.4 1-1-16-16zm3 9v-2h2l2 2h-4z" id="path68"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-rtl.svg
new file mode 100644
index 00000000..8f807596
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/blockUndo-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g66">
+ <path d="M7 11v2h2l-3.6 3.6c-.9-1.3-1.4-2.9-1.4-4.6 0-4.4 3.6-8 8-8 1.7 0 3.3.5 4.6 1.4l-5.6 5.6h-4zm13-7l1 1-2.4 2.4c.9 1.3 1.4 2.9 1.4 4.6 0 4.4-3.6 8-8 8-1.7 0-3.3-.5-4.6-1.4l-2.4 2.4-1-1 16-16zm-3 9v-2h-2l-2 2h4z" id="path68"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-a.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-a.svg
new file mode 100644
index 00000000..4b828779
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-a.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-a">
+ <path d="M16 18h3l-5-12h-3l-5 12h3l1.25-3h4.5l1.25 3zm-4.917-5l1.417-3.4 1.417 3.4h-2.834z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-ain.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-ain.svg
new file mode 100644
index 00000000..f96cebce
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-ain.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-arab-ain">
+ <path id="arab-ain" d="M9.337 13.616c0 1.349 1.386 2.101 4.159 2.258l2.187-.029.318.044c-.03.127-.251.345-.665.652l-.089.066c-1.236.929-2.423 1.393-3.56 1.393-1.143 0-2.046-.33-2.711-.99-.65-.66-.975-1.559-.975-2.698.005-1.354.566-2.573 1.684-3.658v-.044l-.606-.55c-.148-.181-.222-.391-.222-.63 0-.489.239-1.109.717-1.862.65-1.046 1.303-1.566 1.958-1.561.886.005 1.618.42 2.194 1.246.325.479-.03.552-1.064.22-.842-.327-1.527-.051-2.054.828l.015.073 1.123.865.052.007c1.404-.498 2.418-.74 3.043-.726-.059.117-.14.362-.244.733-.103.357-.204.684-.303.982l-.126.374-.384.051c-1.743.239-2.992.716-3.745 1.429-.463.464-.697.973-.702 1.525"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-dad.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-dad.svg
new file mode 100644
index 00000000..f04c6aad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-arab-dad.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-arab-dad">
+ <path id="arab-dad" d="M16.411 8.232l-1.676-.665.694-1.567 1.688.64-.707 1.592m.775 3.078c-.509-.286-1-.427-1.476-.423-.471 0-.982.205-1.532.616l-.506.379.006.025c1.084.066 1.934.099 2.551.099h.313c.567-.021.992-.064 1.276-.131-.067-.17-.275-.359-.625-.566h-.006m-6.803 3.296c-.017-.904-.329-1.87-.938-2.898l1.294-1.729.119.149c.267.336.504.924.713 1.766l.063.05c.496-.008.942-.17 1.338-.485v-.006l1.732-1.53c.679-.601 1.282-.902 1.807-.902.383.004.848.195 1.394.572.55.377.884.696 1 .958.063.149.094.386.094.709 0 .696-.11 1.229-.331 1.598-.192.311-.473.555-.844.734-.438.207-1.549.311-3.333.311-.8 0-1.795-.021-2.983-.062l-.144.429c-.254.672-.463 1.113-.625 1.324-.725.937-1.786 1.405-3.183 1.405-1.705-.008-2.557-.922-2.557-2.742.004-.941.279-1.814.825-2.618.15-.216.298-.367.444-.454.225-.133.288-.091.188.124-.396.862-.596 1.548-.6 2.058.008 1.177.752 1.768 2.232 1.772 1.038-.004 1.803-.182 2.295-.535"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-armn-to.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-armn-to.svg
new file mode 100644
index 00000000..4dbec6d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-armn-to.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-armn-to">
+ <path id="armn-to" d="M13.86 16.257c.124 0 .254-.026.39-.078.135-.058.257-.15.367-.274.114-.13.205-.302.273-.516.073-.213.11-.48.11-.797V13h-1.14c-.14 0-.284.026-.43.078-.14.047-.27.133-.383.258-.11.125-.2.294-.274.508-.067.213-.1.487-.1.82 0 .34.035.47.108.695.08.218.175.395.29.53.12.136.247.232.383.29.14.05.276.077.406.077m-2.97-7.84c-.37.082-.695.247-.976.45-.28.198-.505.47-.672.813-.16.343-.242.78-.242 1.312V18H6v-7.188c0-.776.15-1.455.453-2.04.302-.587.714-1.077 1.234-1.467.52-.39 1.13-.685 1.83-.883.697-.198 1.44-.297 2.225-.297.526 0 1.04.044 1.54.133.504.088.98.22 1.43.398.447.172.858.388 1.233.65.375.26.698.564.97.913.275.348.49.738.64 1.17.15.433.226 1.094.226 1.61h1.353v2.04H17.78v1.6c0 .58-.103 1.092-.31 1.54-.21.442-.49.815-.845 1.117-.35.302-.834.53-1.297.687-.464.15-.953.226-1.47.226-.51 0-.996-.078-1.46-.234-.464-.156-.87-.39-1.22-.703-.348-.313-.626-.703-.835-1.172-.203-.473-.304-1.028-.304-1.663s.105-1.182.32-1.64c.213-.46.497-.685.85-.977.355-.297.76-.513 1.22-.648.458-.14.935-.21 1.43-.21h1.132c-.01-.49-.04-1.043-.242-1.36-.198-.323-.453-.58-.766-.766-.312-.193-.598-.332-.984-.426-.374-.09-.577-.094-1.1-.094-.52 0-.64.02-1.01.102z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-b.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-b.svg
new file mode 100644
index 00000000..4f648203
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-b.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-b">
+ <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.011-1.975-1.989-3 2-.975 1.989-1.935 1.989-3 0-2-2-3-4-3h-6v12zm7-8c0 1.001 0 1-2 1h-2v-3h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-be.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-be.svg
new file mode 100644
index 00000000..279466d4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-be.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-cyrl-be">
+ <path id="cyrl-be" d="M7 6h9v2h-6v3h2.649c.893 0 1.633.109 2.22.327.588.218 1.088.622 1.502 1.211.419.589.629 1.187.629 1.978 0 .813-.21 1.398-.629 1.977-.419.578-.898.974-1.437 1.187-.533.213-1.295.319-2.286.319h-5.649m4.767-2c.751 0 1.279-.049 1.584-.12.305-.076.569-.246.792-.508.229-.262.343-.473.343-.855 0-.557-.199-.868-.596-1.119-.392-.256-1.064-.398-2.016-.398h-1.873v3"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-te.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-te.svg
new file mode 100644
index 00000000..fdeeb6c5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-te.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-cyrl-te">
+ <path id="te" d="M11 18v-10h-4v-2h11v2h-4v10"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-zhe.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-zhe.svg
new file mode 100644
index 00000000..5996c813
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-cyrl-zhe.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-cyrl-zhe">
+ <path id="cyrl-zhe" d="M13 6v5.154c.328-.033.537-.181.705-.447.168-.266.401-.873.698-1.821.39-1.241.789-2.033 1.197-2.374.403-.336 1.075-.504 2.014-.504l.386-.008v1.78l-.386-.008c-.399 0-.691.062-.878.187-.186.119-.337.304-.452.553-.115.249-.286.762-.512 1.537-.12.412-.25.756-.392 1.033-.137.276-.383.537-.738.78.439.157.8.466 1.084.927.288.455.603 1.103.944 1.943l1.33 3.268h-2.314l-1.17-3.081-.113-.252-.239-.561c-.248-.569-.452-.932-.612-1.089-.16-.157-.317-.236-.552-.236v5.22h-2v-5.22c-.226 0-.382.076-.546.228-.164.152-.368.518-.612 1.098l-.246.561-.113.252-1.17 3.081h-2.314l1.33-3.268c.328-.808.636-1.447.924-1.919.293-.477.663-.794 1.11-.951-.355-.244-.603-.501-.745-.772-.137-.276-.268-.623-.392-1.041-.222-.759-.39-1.266-.505-1.52-.111-.255-.261-.444-.452-.569-.186-.125-.492-.187-.917-.187l-.352.008v-1.78l.386.008c.953 0 1.631.171 2.034.512.399.347.791 1.136 1.177 2.366.301.954.534 1.564.698 1.829.168.26.377.406.705.439v-5.154"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-f.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-f.svg
new file mode 100644
index 00000000..357d2e5d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-f.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-f">
+ <path id="f" d="M16 8v-2h-8v12h3v-5h4v-2h-4v-3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-g.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-g.svg
new file mode 100644
index 00000000..e032542e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-g.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-g">
+ <path id="g" d="M12 14v-2h5v4.203c-.497.475-1.22.894-2.166 1.259-.941.359-1.896.538-2.864.538-1.23 0-2.303-.253-3.217-.76-.915-.512-1.602-1.24-2.062-2.185-.46-.95-.69-1.982-.69-3.095 0-1.208.257-2.282.77-3.222.513-.939 1.265-1.66 2.255-2.161.754-.385 1.693-.578 2.816-.578 1.46 0 2.6.303 3.418.91.824.602 1.353 1.435 1.589 2.501l-2.359.435c-.166-.57-.479-1.018-.939-1.346-.455-.332-1.024-.499-1.709-.499-1.038 0-1.864.325-2.479.974-.61.649-.915 1.612-.915 2.889 0 1.377.31 2.412.931 3.103.62.686 1.433 1.029 2.439 1.029.497 0 .995-.095 1.492-.285.503-.195 1.332-.571 1.691-.845v-.867"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-geor-man.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-geor-man.svg
new file mode 100644
index 00000000..b211bf7a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-geor-man.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-geor-man">
+ <path id="geor-man" d="M13.832 14.061c0-1.715-.394-2.573-1.182-2.573-.868 0-1.302.779-1.302 2.338-.01 1.624.421 2.436 1.295 2.436.793 0 1.189-.734 1.189-2.201m2.168 0c0 2.626-1.116 3.939-3.349 3.939-2.434 0-3.651-1.386-3.651-4.159 0-2.738 1.217-4.106 3.651-4.106.841 0 1.182.63 1.182.63v-1.579c0-.789-.449-1.184-1.347-1.184-.572 0-.858.374-.858 1.123h-2.341c.005-1.817 1.064-2.725 3.176-2.725 2.368 0 3.548.946 3.538 2.839"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-l.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-l.svg
new file mode 100644
index 00000000..16797938
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-l.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-l">
+ <path id="l" d="M8 18v-12h3v10h5v2"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-n.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-n.svg
new file mode 100644
index 00000000..73ad019a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-n.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-n">
+ <path id="n" d="M7 18v-12h3l4 8v-8h3v12h-3l-4-8v8h-3"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-v.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-v.svg
new file mode 100644
index 00000000..146943a5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/bold-v.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-v">
+ <path id="v" d="M10.5 18l-4.5-12h3l3 8 3-8h3l-4.5 12"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/cancel.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/cancel.svg
new file mode 100644
index 00000000..bfc1b44b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/cancel.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="cancel">
+ <path id="circle-with-strike" d="M11.999 5.022c-3.853 0-6.977 3.124-6.977 6.978 0 3.853 3.124 6.978 6.977 6.978 3.854 0 6.979-3.125 6.979-6.978 0-3.854-3.125-6.978-6.979-6.978zm-5.113 6.978c0-1.092.572-3.25.93-2.929l7.113 7.113c.488.525-1.837.931-2.93.931-2.825-.001-5.113-2.291-5.113-5.115zm9.298 2.929l-7.114-7.113c-.445-.483 1.837-.931 2.929-.931 2.827 0 5.115 2.289 5.115 5.114 0 1.093-.364 3.543-.93 2.93z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-ltr.svg
new file mode 100644
index 00000000..f31ec095
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8l-8.8 8.9z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-rtl.svg
new file mode 100644
index 00000000..02b4e387
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caret-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M16.5 13.1l-8.9 8.9c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z" id="path108"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretDown.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretDown.svg
new file mode 100644
index 00000000..a04ca572
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretDown.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 16l8.9-8.9c-.8-.8-2-.8-2.8 0l-6.1 6.1-6.1-6c-.8-.8-2-.8-2.8 0l8.9 8.8z" id="path4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretUp.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretUp.svg
new file mode 100644
index 00000000..d0e0c283
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/caretUp.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 6.5l8.9 8.9c-.8.8-2 .8-2.8 0l-6.1-6.1-6.1 6c-.8.8-2 .8-2.8 0l8.9-8.8z" id="path4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/case-sensitive.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/case-sensitive.svg
new file mode 100644
index 00000000..824790c5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/case-sensitive.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="regular-expression">
+ <path id="upper-case" d="M 7.53125,7 4,17 l 2.0625,0 0.71875,-2.40625 3.625,0 L 11.125,17 13.1875,17 9.65625,7 7.53125,7 z M 8.59375,8.53125 9.9375,13 7.25,13 8.59375,8.53125 z" />
+ <path id="lower-case" d="m 18.548697,17 -0.183254,-1.035072 -0.05451,0 c -0.349771,0.440361 -0.710892,0.746796 -1.083366,0.919307 -0.367941,0.167972 -0.849436,0.251959 -1.444489,0.251959 -0.564328,0 -0.954665,-0.20883 -1.377109,-0.626492 -0.417903,-0.417659 -0.626854,-1.012371 -0.626853,-1.784137 -1e-6,-0.80808 0.281628,-1.402791 0.844889,-1.784137 0.567801,-0.385878 1.193222,-0.607062 2.208372,-0.640111 l 1.321843,-0.04086 0,-0.333674 c 0,-0.771759 -0.395195,-1.15764 -1.185571,-1.157647 -0.608688,7e-6 -1.324118,0.183867 -2.146293,0.551584 L 14.134181,9.9184512 c 0.876685,-0.4585114 1.848761,-0.6877705 2.916233,-0.6877783 1.022038,7.8e-6 1.586855,0.2224573 2.131951,0.6673492 C 19.727448,10.342928 20,11.019356 20,11.927309 l 0,5.073215 -1.451303,0 m -0.394476,-3.527417 -0.804008,0.02724 c -0.604145,0.01816 -1.053844,0.127119 -1.349098,0.326866 -0.29526,0.199753 -0.442889,0.503919 -0.442886,0.912498 -3e-6,0.585634 0.336136,0.878451 1.008417,0.878449 0.481492,2e-6 0.865326,-0.138462 1.151503,-0.415391 0.29071,-0.276925 0.436067,-0.644648 0.436072,-1.103169 l 0,-0.626491" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/check.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/check.svg
new file mode 100644
index 00000000..03e36607
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/check.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="check">
+ <path d="M7.105 13.473l1.422-1.423 1.901 1.902 4.81-6.952 1.657 1.148-6.26 8.852z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/circle.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/circle.svg
new file mode 100644
index 00000000..436259e5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/circle.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="6"></circle></svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/close.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/close.svg
new file mode 100644
index 00000000..1345e867
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/close.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="close">
+ <path id="x" d="M18.717 6.697l-1.414-1.414-5.303 5.303-5.303-5.303-1.414 1.414 5.303 5.303-5.303 5.303 1.414 1.414 5.303-5.303 5.303 5.303 1.414-1.414-5.303-5.303z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/code.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/code.svg
new file mode 100644
index 00000000..32f140d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/code.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
+ <g id="code">
+ <path id="left-bracket" d="M4 12v-1h1c1 0 1 0 1-1v-2.386c0-.514.024-.896.073-1.142.054-.252.139-.463.257-.633.204-.279.473-.475.808-.584.335-.115.872-.255 1.835-.255h1.027v1h-.752c-.457 0-.77.191-.936.408-.167.215-.312.445-.312 1.068v1.857c0 .729-.041 1.18-.244 1.493-.2.307-.562.529-1.09.667.535.155.9.385 1.096.688.199.303.238.757.238 1.484v1.862c0 .619.145.848.312 1.062.166.22.479.407.936.407l.752.004v1h-1.027c-.963 0-1.5-.133-1.835-.248-.335-.109-.604-.307-.808-.591-.118-.165-.203-.374-.257-.625-.049-.253-.073-.636-.073-1.149v-2.387c0-1 0-1-1-1h-1z"/>
+ <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" width="24" height="24" xlink:href="#left-bracket"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/collapse.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/collapse.svg
new file mode 100644
index 00000000..55aa8f8f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/collapse.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="collapse">
+ <path id="arrow" d="M6.697 15.714l5.303-5.302 5.303 5.302 1.414-1.414-6.717-6.717-6.717 6.717z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/comment.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/comment.svg
new file mode 100644
index 00000000..0ae7e63f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/comment.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="comment">
+ <path id="speech-bubble" d="M15 6h-6c-1.657 0-3 1.344-3 3v4c0 1.656 1.343 3 3 3v3l3-3h3c1.657 0 3-1.344 3-3v-4c0-1.656-1.343-3-3-3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/downTriangle.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/downTriangle.svg
new file mode 100644
index 00000000..7bc1c228
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/downTriangle.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 18l8-10h-16z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-ltr.svg
new file mode 100644
index 00000000..3972e070
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_3">
+ <path d="M17 2l-12 12-1 5 5-1 12-12c0-2-2-4-4-4zm-9.8 13.5c-.3-.3-.7-.6-1-.8 2.3-2.3 11.3-11.4 11.3-11.4.4.1.7.3 1 .7l-11.3 11.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-rtl.svg
new file mode 100644
index 00000000..978b2fd1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/edit-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_3">
+ <path d="M8 2l12 12 1 5-5-1-12-12c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8-2.3-2.3-11.3-11.4-11.3-11.4-.4.1-.7.3-1 .7l11.3 11.5z" id="path173"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-ltr.svg
new file mode 100644
index 00000000..7e376824
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-ltr.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_2">
+ <g id="g184">
+ <path d="M21 4v-1s0-3-3-3-3 3-3 3v1h-1v6h8v-6zm-1.5 0h-3v-1s0-1.5 1.5-1.5c1.48.06 1.5 1.5 1.5 1.5zm-6.5 5.6l-6.8 6.9c-.3-.3-.7-.6-1-.8 1.4-1.4 5-5 7.8-7.9v-1.8l-9 9-1 5 5-1 8-8h-3z" id="path186"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-rtl.svg
new file mode 100644
index 00000000..0b4751d2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editLock-rtl.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_2">
+ <g id="g184">
+ <path d="M4 4v-1s0-3 3-3 3 3 3 3v1h1v6h-8v-6zm1.5 0h3v-1s0-1.5-1.5-1.5c-1.48.06-1.5 1.5-1.5 1.5zm6.5 5.6l6.8 6.9c.3-.3.7-.6 1-.8-1.4-1.4-5-5-7.8-7.9v-1.8l9 9 1 5-5-1-8-8h3z" id="path186"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-ltr.svg
new file mode 100644
index 00000000..f346874e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-ltr.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g196">
+ <g id="g198">
+ <path d="M14.9 2.8c.9 0 1.8.2 2.7.6.9.4 1.6.9 1.9 1.6-2.8.1-5 1.1-6.6 3.1l1.3 2-6.7-.3.5-6.8 1.7 2c1.8-1.5 3.5-2.2 5.2-2.2z" id="path200"/>
+ </g>
+ </g>
+ <g id="g204">
+ <path d="M15.2 11.1l-2.6-.1-5.4 5.5c-.3-.3-.7-.6-1-.8.9-.9 2.8-2.8 4.7-4.8h-1.8l-4.1 4.1-1 5 5-1 7.8-7.8-1.6-.1zm5.4-5.1c-1.7 0-3.2.5-4.4 1.4l-.9.9.8 1.3.9 1.4 4-4c0-.3-.1-.7-.2-1h-.2z" id="path206"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-rtl.svg
new file mode 100644
index 00000000..5b59d452
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/editUndo-rtl.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g196">
+ <g id="g198">
+ <path d="M10.1 2.8c-.9 0-1.8.2-2.7.6-.9.4-1.6.9-1.9 1.6 2.8.1 5 1.1 6.6 3.1l-1.3 2 6.7-.3-.5-6.8-1.7 2c-1.8-1.5-3.5-2.2-5.2-2.2z" id="path200"/>
+ </g>
+ </g>
+ <g id="g204">
+ <path d="M9.8 11.1l2.6-.1 5.4 5.5c.3-.3.7-.6 1-.8-.9-.9-2.8-2.8-4.7-4.8h1.8l4.1 4.1 1 5-5-1-7.8-7.8 1.6-.1zm-5.4-5.1c1.7 0 3.2.5 4.4 1.4l.9.9-.8 1.3-.9 1.4-4-4c0-.3.1-.7.2-1h.2z" id="path206"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/ellipsis.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/ellipsis.svg
new file mode 100644
index 00000000..dd36a30d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/ellipsis.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <g>
+ <path d="M8 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+ </g>
+ <g>
+ <path d="M14 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+ </g>
+ <g>
+ <path d="M20 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/expand.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/expand.svg
new file mode 100644
index 00000000..7666b41d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/expand.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="expand">
+ <path id="arrow" d="M17.303 8.283l-5.303 5.303-5.303-5.303-1.414 1.414 6.717 6.717 6.717-6.717z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-ltr.svg
new file mode 100644
index 00000000..827bc1b1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="external">
+ <path id="box" d="M2 2h3v1h-2v6h6v-2h1v3h-8z"/>
+ <path id="arrow" d="M6.211 2h3.789v3.789l-1.421-1.421-2.132 2.132-.947-.947 2.132-2.132z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-rtl.svg
new file mode 100644
index 00000000..c375ca0f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/external-link-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="external">
+ <path id="box" d="M7 3h2v6h-6v-2h-1v3h8v-8h-3z"/>
+ <path id="arrow" d="M2 5.789l1.421-1.421 2.132 2.132.947-.947-2.132-2.132 1.421-1.421h-3.789z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-ltr.svg
new file mode 100644
index 00000000..f8578cf8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="find">
+ <path id="magnifying-glass" d="m 13.65625,11 c -1.921,0 -3.5,1.54775 -3.5,3.46875 0,1.92 1.579,3.5 3.5,3.5 0.749,0 1.432,-0.25225 2,-0.65625 l 0.09375,0.15625 2.375,2.375 c 0.19,0.189 0.53425,0.15325 0.78125,-0.09375 0.247,-0.247 0.314,-0.59125 0.125,-0.78125 l -2.375,-2.375 L 16.46875,16.5 C 16.87175,15.934 17.125,15.21775 17.125,14.46875 17.124,12.54875 15.57525,11 13.65625,11 z m 0,1.65625 c 1.011306,0 1.8125,0.801194 1.8125,1.8125 0,1.011306 -0.801194,1.84375 -1.8125,1.84375 -1.011306,0 -1.84375,-0.832444 -1.84375,-1.84375 0,-1.011306 0.832444,-1.8125 1.84375,-1.8125 z" />
+ <path id="text" d="M 6,5 6,7 16,7 16,5 6,5 z m 0,3 0,2 11,0 0,-2 -11,0 z m 0,3 0,2 3.53125,0 c 0.2825289,-0.797203 0.786096,-1.486208 1.4375,-2 L 6,11 z m 0,3 0,2 3.53125,0 C 9.3537004,15.520243 9.25,15.010236 9.25,14.46875 9.25,14.309811 9.2962033,14.154621 9.3125,14 L 6,14 z" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-rtl.svg
new file mode 100644
index 00000000..2a1e9c6f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/find-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="find">
+ <path id="magnifying-glass" d="m 11.343828,11.000025 c 1.921,0 3.5,1.54775 3.5,3.46875 0,1.92 -1.579,3.5 -3.5,3.5 -0.749,0 -1.432,-0.25225 -2,-0.65625 l -0.09375,0.15625 -2.375,2.375 c -0.19,0.189 -0.53425,0.15325 -0.78125,-0.09375 -0.247,-0.247 -0.314,-0.59125 -0.125,-0.78125 l 2.375,-2.375 0.1875,-0.09375 c -0.403,-0.566 -0.65625,-1.28225 -0.65625,-2.03125 10e-4,-1.92 1.54975,-3.46875 3.46875,-3.46875 z m 0,1.65625 c -1.011306,0 -1.8125,0.801194 -1.8125,1.8125 0,1.011306 0.801194,1.84375 1.8125,1.84375 1.011306,0 1.84375,-0.832444 1.84375,-1.84375 0,-1.011306 -0.832444,-1.8125 -1.84375,-1.8125 z" />
+ <path id="text" d="M 19,5 19,7 9,7 9,5 z m 0,3 0,2 -11,0 0,-2 z m 0,3 0,2 -3.53125,0 c -0.282529,-0.797203 -0.786096,-1.486208 -1.4375,-2 z m 0,3 0,2 -3.53125,0 C 15.6463,15.520243 15.75,15.010236 15.75,14.46875 15.75,14.309811 15.703797,14.154621 15.6875,14 z" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-ltr.svg
new file mode 100644
index 00000000..6e81d2bc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M14 6.5v-1.5c-1.4-1.5-5.2-1.2-6 0v-1h-1v15h1v-7c.8-.8 3.4-.9 5-.5v1.5c1.2 1.5 4.3 1.2 5 0v-7c-.7.7-2.7.9-4 .5z" id="path216"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-rtl.svg
new file mode 100644
index 00000000..4b743aac
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flag-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M11 6.5v-1.5c1.4-1.5 5.2-1.2 6 0v-1h1v15h-1v-7c-.8-.8-3.4-.9-5-.5v1.5c-1.2 1.5-4.3 1.2-5 0v-7c.7.7 2.7.9 4 .5z" id="path216"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-ltr.svg
new file mode 100644
index 00000000..49cdb7a2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-ltr.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g2990">
+ <g id="Layer_1">
+ <path id="path227" d="M14 6.5v-1.5c-1.4-1.5-5.2-1.2-6 0v-1h-1v15h1v-7c.8-.8 3.4-.9 5-.5v1.5c1.2 1.5 4.3 1.2 5 0v-7c-.7.7-2.7.9-4 .5z"/>
+ </g>
+ <g id="Layer_2">
+ <g id="g230">
+ <path id="path232" d="M17.997 1.989l.99.99-15.98 15.98-.99-.99z"/>
+ </g>
+ <g id="g234">
+ <path id="path236" d="M16.999 1.016l.99.99-15.98 15.98-.99-.99z" fill="#fff"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-rtl.svg
new file mode 100644
index 00000000..e470de42
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/flagUndo-rtl.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g2990">
+ <g id="Layer_1">
+ <path id="path227" d="M11 6.5v-1.5c1.4-1.5 5.2-1.2 6 0v-1h1v15h-1v-7c-.8-.8-3.4-.9-5-.5v1.5c-1.2 1.5-4.3 1.2-5 0v-7c.7.7 2.7.9 4 .5z"/>
+ </g>
+ <g id="Layer_2">
+ <g id="g230">
+ <path id="path232" d="M7.003 1.989l-.99.99 15.98 15.98.99-.99z"/>
+ </g>
+ <g id="g234">
+ <path id="path236" d="M8.001 1.016l-.99.99 15.98 15.98.99-.99z" fill="#fff"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-ltr.svg
new file mode 100644
index 00000000..bb2545c5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-ltr.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="help">
+ <path id="circle" d="M12.001 2.085c-5.478 0-9.916 4.438-9.916 9.916 0 5.476 4.438 9.914 9.916 9.914 5.476 0 9.914-4.438 9.914-9.914 0-5.478-4.438-9.916-9.914-9.916zm.001 18c-4.465 0-8.084-3.619-8.084-8.083 0-4.465 3.619-8.084 8.084-8.084 4.464 0 8.083 3.619 8.083 8.084 0 4.464-3.619 8.083-8.083 8.083z"/>
+ <g id="question-mark">
+ <path id="top" d="M11.766 6.688c-2.5 0-3.219 2.188-3.219 2.188l1.411.854s.298-.791.901-1.229c.516-.375 1.625-.625 2.219.125.701.885-.17 1.587-1.078 2.719-.953 1.186-1 3.655-1 3.655h1.969s.135-2.318 1.041-3.381c.603-.707 1.443-1.338 1.443-2.494s-1.187-2.437-3.687-2.437z"/>
+ <path id="bottom" d="M11 16h2v2h-2z"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-rtl.svg
new file mode 100644
index 00000000..99c7f842
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/help-rtl.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="help">
+ <path id="circle" d="M11.999 2.085c5.478 0 9.916 4.438 9.916 9.916 0 5.476-4.438 9.914-9.916 9.914-5.476 0-9.914-4.438-9.914-9.914 0-5.478 4.438-9.916 9.914-9.916zm-.001 18c4.465 0 8.084-3.619 8.084-8.083 0-4.465-3.619-8.084-8.084-8.084-4.464 0-8.083 3.619-8.083 8.084 0 4.464 3.619 8.083 8.083 8.083z"/>
+ <g id="question-mark">
+ <path id="top" d="M12.234 6.688c2.5 0 3.219 2.188 3.219 2.188l-1.411.854s-.298-.791-.901-1.229c-.516-.375-1.625-.625-2.219.125-.701.885.17 1.587 1.078 2.719.953 1.186 1 3.655 1 3.655h-1.969s-.135-2.318-1.041-3.381c-.603-.707-1.443-1.338-1.443-2.494 0-1.156 1.187-2.437 3.687-2.437z"/>
+ <path id="bottom" d="M13 16h-2v2h2z"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/history.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/history.svg
new file mode 100644
index 00000000..35f15afe
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/history.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="history">
+ <path id="clock-hands" d="M17.26 15.076s-2.385-1.935-4.005-3.062c.72-2.397 1.702-6.559 1.702-6.559s-4.35 5.363-4.877 6.699c-.463 1.168 1.459 2.209 2.346 1.678 1.9.551 4.834 1.244 4.834 1.244z"/>
+ <path id="arrow" d="M12.086 2.085c-5.478 0-9.916 4.438-9.916 9.916 0 1.783.476 3.454 1.301 4.898l-2.223 2.04h5.688v-5.219l-2.066 1.896c-.55-1.088-.866-2.312-.866-3.615 0-4.465 3.619-8.084 8.084-8.084 4.464 0 8.083 3.619 8.083 8.084 0 4.464-3.619 8.083-8.083 8.083-1.145 0-2.228-.247-3.213-.678l-.833 1.634c1.235.557 2.602.874 4.045.874 5.476 0 9.914-4.438 9.914-9.914-.001-5.477-4.439-9.915-9.915-9.915z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-ltr.svg
new file mode 100644
index 00000000..e95d40d8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-ltr.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="indent-list-ltr">
+ <path id="arrow" d="M5 15.079l4.794-3.527-4.704-3.599-.01 2.047h-2.08v3h2z"/>
+ <path id="bottom_line" d="M20 17h-16c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h16c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1z"/>
+ <path id="middle_line" d="M20 10h-7c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h7c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1z"/>
+ <path id="top_line" d="M20 3h-16c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h16c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-rtl.svg
new file mode 100644
index 00000000..cca3ad31
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/indent-rtl.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="indent-list-rtl">
+ <path id="arrow" d="M19 15.079l-4.794-3.527 4.704-3.599.01 2.047h2.08v3h-2z"/>
+ <path id="bottom_line" d="M4 17h16c.553 0 1 .447 1 1v1c0 .553-.447 1-1 1h-16c-.553 0-1-.447-1-1v-1c0-.553.447-1 1-1z"/>
+ <path id="middle_line" d="M4 10h7c.553 0 1 .447 1 1v1c0 .553-.447 1-1 1h-7c-.553 0-1-.447-1-1v-1c0-.553.447-1 1-1z"/>
+ <path id="top_line_5_" d="M4 3h16c.553 0 1 .447 1 1v1c0 .553-.447 1-1 1h-16c-.553 0-1-.447-1-1v-1c0-.553.447-1 1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/info.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/info.svg
new file mode 100644
index 00000000..9c0d1cbc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/info.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0, 0, 24, 24">
+ <g id="info">
+ <path id="circled-i" d="M11.499 17c-3.036 0-5.499-2.464-5.499-5.5 0-3.037 2.462-5.5 5.499-5.5 3.037 0 5.501 2.462 5.501 5.5 0 3.036-2.464 5.5-5.501 5.5zm.002-12c-3.591 0-6.501 2.91-6.501 6.5s2.91 6.5 6.501 6.5c3.588 0 6.499-2.911 6.499-6.5s-2.911-6.5-6.499-6.5zM12 10v4h1v1h-3v-1h1v-3h-1v-1zM11 8h1v1h-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/insert.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/insert.svg
new file mode 100644
index 00000000..0833f84f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/insert.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="insert">
+ <path d="M13 5h-2v6h-6v2h6v6h2v-6h6v-2h-6z" id="plus"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-a.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-a.svg
new file mode 100644
index 00000000..a0e66bff
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-a.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-a">
+ <path id="a" d="M14.667 6h-1.372l-7 12h1.705l2.333-4h4l.667 4h1.667l-2-12zm-3.75 7l2.527-4.333.723 4.333h-3.25z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-keheh-jeem.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-keheh-jeem.svg
new file mode 100644
index 00000000..d4bff1be
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-keheh-jeem.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-arab-keheh-jeem">
+ <path id="arab-keheh-jeem" d="M18.125 5.844c-1.695.555-3.297 1.162-4.594 1.938-.49.299-.774.712-.875 1.125-.064.263-.035.572.063.781.189.405.539.574.844.813l.094-.125.531.625c.14.164.343.513.469.938.137.463.08.725 0 1.125h-3.438c-.338 0-.592.007-.766-.02-.339-.053-.256-.208-.234-.34.332-.127.564-.173.938-.141.29-.494.593-.885.906-1.313-.98.037-1.878.015-2.688-.094-.346-.047-.698-.186-1.094-.156-.357.026-.768.239-1.031.719-.246.448-.434.839-.656 1.281l.75-.469c.23-.142.484-.227.719-.219.157.005.275.054.406.094-.231.205-.509.402-.719.563-.301.26-.702.688-.906 1-.403.615-.694 1.084-.875 1.781-.179.689.004 1.339.469 1.75.426.376.846.519 1.281.563.65.065 1.205.093 2-.188.657-.231 1.021-.553 1.5-.969-.883.11-1.817.089-2.531.031-.871-.07-1.268-.384-1.469-.594-.271-.283-.307-.64-.156-1.219.036-.141.097-.323.25-.531.168-.228.364-.435.594-.656.451-.436 1.011-.737 1.461-.938-.045.206-.107.443-.055.688.049.229.248.379.438.469.259.122.506.155.688.156 1.421.011 2.862 0 4.281 0 .247 0 .452-.163.594-.375.139-.208.249-.481.344-.844.131-.499.094-1.062-.094-1.625-.182-.543-.418-1.009-.719-1.406-.335-.443-.674-.829-1-1.219 1.257-.815 2.716-1.239 3.969-1.688.121-.452.224-.926.313-1.313zm-9.469 8.438c-.262.394-.584.691-.875 1 .375.286.748.556 1.094.813.335-.303.626-.674.875-.969-.39-.268-.771-.588-1.094-.844z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-meem.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-meem.svg
new file mode 100644
index 00000000..bfbc9bf5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-arab-meem.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-arab-meem">
+ <path id="arab-meem" d="M16 9.729l-.93 2.19h-4.663c-.479 0-.857.122-1.135.367l-.061.11c-.184 2.016-.502 3.558-.955 4.627-.272.641-.633 1.252-1.082 1.833-.177.226-.219.186-.126-.119l.142-.504.17-.669.234-.87.002-.009.202-1.045.258-1.411.353-1.906c.191-.312.424-.638.699-.98.276-.342.589-.706.94-1.09.129-.092.697-.18 1.705-.266 1.05-.086 1.638-.183 1.765-.293l.065-.128c.007-.11-.011-.241-.054-.394-.043-.153-.12-.327-.231-.522-.22-.428-.438-.641-.654-.641-.294 0-.915.269-1.864.806-.359.208-.376.125-.051-.247 1.558-1.71 2.708-2.566 3.45-2.566.383 0 .671.131.863.394.135.195.25.599.344 1.21l.203 1.2c.106.586.242.895.409.925"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-armn-sha.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-armn-sha.svg
new file mode 100644
index 00000000..63de0f6c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-armn-sha.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-armn-sha">
+ <path id="armn-sha" d="M11.564 7.678c-.268-.13-.578-.22-.93-.268-.35-.047-.75-.07-1.197-.07h-1.11L8.586 6h1.724c.558 0 1.042.032 1.45.095.416.063.794.173 1.136.33l4.483 2.033-.324 1.67-2.624-1.165c-.126-.058-.27-.103-.433-.134-.164-.038-.356-.057-.576-.057-.583 0-1.137.095-1.663.284-.524.19-1 .46-1.425.812-.42.35-.777.78-1.072 1.283-.294.504-.504 1.074-.63 1.71-.242 1.255-.152 2.21.268 2.868.426.652 1.19.978 2.294.978.55 0 1.045-.08 1.48-.237.437-.156.815-.377 1.136-.66.326-.29.59-.633.796-1.033.21-.4.362-.84.457-1.323l.11-.56h1.6l-.12.59c-.13.674-.356 1.288-.676 1.845-.32.55-.725 1.026-1.214 1.425-.488.394-1.053.7-1.694.922-.642.215-1.343.323-2.105.323-.767 0-1.434-.113-2-.34-.568-.225-1.025-.553-1.372-.984-.347-.436-.573-.97-.678-1.607-.105-.637-.078-1.364.08-2.184.125-.66.346-1.273.66-1.835.316-.567.697-1.066 1.144-1.496.445-.436.944-.794 1.496-1.072.55-.284 1.13-.475 1.733-.575l-.466-.23"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-c.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-c.svg
new file mode 100644
index 00000000..b468deac
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-c.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-c">
+ <path id="c" d="M15.008 13.718l1.481.214c-.468 1.34-1.15 2.354-2.046 3.04-.896.686-1.901 1.029-3.015 1.029-1.359 0-2.438-.43-3.237-1.29-.794-.86-1.191-2.092-1.191-3.697 0-2.09.606-3.818 1.817-5.185 1.079-1.219 2.42-1.828 4.023-1.828 1.186 0 2.145.33 2.878.989.738.66 1.165 1.546 1.282 2.66l-1.397.135c-.148-.839-.453-1.464-.916-1.876-.458-.417-1.051-.625-1.779-.625-1.369 0-2.476.631-3.321 1.892-.733 1.087-1.099 2.377-1.099 3.871 0 1.193.282 2.103.847 2.731.565.628 1.3.942 2.206.942.774 0 1.473-.261 2.099-.784.626-.522 1.081-1.261 1.366-2.216"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-d.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-d.svg
new file mode 100644
index 00000000..92a834d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-d.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-d">
+ <path id="d" d="M7 18l2.462-12h3.557c.853 0 1.505.063 1.955.188.644.169 1.194.472 1.65.909.456.431.799.971 1.03 1.621.231.649.346 1.378.346 2.186 0 .966-.145 1.847-.435 2.644-.284.791-.66 1.49-1.127 2.095-.461.6-.947 1.072-1.456 1.416-.504.338-1.102.589-1.794.753-.526.126-1.172.188-1.939.188h-4.249m1.859-1.359h1.867c.842 0 1.591-.079 2.245-.237.408-.098.756-.243 1.046-.434.381-.246.727-.57 1.038-.974.408-.535.732-1.143.974-1.825.247-.688.37-1.468.37-2.341 0-.971-.166-1.716-.499-2.235-.333-.524-.756-.87-1.271-1.04-.381-.126-.974-.188-1.778-.188h-1.85l-1.907 9.274"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-e.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-e.svg
new file mode 100644
index 00000000..66a5ef5d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-e.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-e">
+ <path id="e" d="M7 18l2.474-12h8.526l-.282 1.367h-6.947l-.75 3.633h6.09l-.282 1.367h-6.09l-.877 4.274h7.438l-.282 1.359h-9.018"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-geor-kan.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-geor-kan.svg
new file mode 100644
index 00000000..3398904d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-geor-kan.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-geor-kan">
+ <path id="geor-kan" d="M15.057 14.663c-.441 2.225-1.834 3.337-4.178 3.337-1.919 0-2.879-.787-2.879-2.36 0-.298.036-.624.108-.977.083-.431.245-.836.488-1.217l1.241.605-.207.613c-.055.259-.083.497-.083.712 0 .972.521 1.458 1.564 1.458 1.307 0 2.101-.723 2.383-2.17l.058-.331c.044-.221.066-.425.066-.613 0-.928-.546-1.391-1.638-1.391h-1.117l.248-1.259h1.117c1.202-.005 1.908-.552 2.118-1.64.039-.182.058-.356.058-.522 0-1.143-.899-1.714-2.697-1.714l.232-1.193c2.708 0 4.062.875 4.062 2.625 0 .248-.028.516-.083.803-.204 1.093-1.051 1.825-2.54 2.195l-.033.166c1.23.199 1.845.823 1.845 1.872 0 .21-.025.433-.074.671l-.058.331"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-i.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-i.svg
new file mode 100644
index 00000000..93bec5a6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-i.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-i">
+ <path id="i" d="M12.5 17.999l.249-.994h-1.5l2.509-10.037h1.5l.242-.967h-5l-.242.967h1.5l-2.509 10.037h-1.5l-.249.994z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-k.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-k.svg
new file mode 100644
index 00000000..d4831549
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-k.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-k">
+ <path id="k" d="M12.018 10.652l4.982-4.652h-2l-5.309 5.234 1.309-5.234h-1.5l-3 12h1.5l1.173-4.693 1.54-1.438c.287 4.131 3.287 6.131 3.287 6.131h2s-4-2-3.982-7.348z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-s.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-s.svg
new file mode 100644
index 00000000..4f6364cb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/italic-s.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-s">
+ <path id="s" d="M16.474 6.589l-.302 1.526c-.522-.279-1.041-.488-1.557-.628-.511-.145-1.007-.217-1.487-.217-.935 0-1.679.204-2.231.612-.553.408-.829.95-.829 1.627 0 .372.101.658.302.86.207.196.733.408 1.58.635l.937.232c1.059.274 1.795.622 2.208 1.046.413.418.62 1.007.62 1.766 0 1.167-.46 2.117-1.379 2.851-.914.733-2.12 1.1-3.618 1.1-.615 0-1.232-.062-1.852-.186-.62-.119-1.242-.302-1.867-.55l.318-1.611c.573.356 1.147.625 1.72.806.578.181 1.154.271 1.728.271.976 0 1.759-.217 2.347-.651.589-.434.883-.999.883-1.697 0-.465-.119-.816-.356-1.054-.232-.243-.736-.462-1.511-.658l-.937-.24c-1.069-.279-1.8-.599-2.192-.961-.387-.367-.581-.878-.581-1.534 0-1.152.442-2.094 1.325-2.828.888-.739 2.043-1.108 3.463-1.108.553 0 1.1.049 1.642.147.542.098 1.085.245 1.627.442"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/language.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/language.svg
new file mode 100644
index 00000000..081e49a1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/language.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="language">
+ <path id="japanese" d="M17.533 9.81l.271-.59 1.041.407-.18.363c.661.271 1.101.468 1.312.589.331.211.618.514.86.905.211.393.316.846.316 1.358 0 .786-.302 1.479-.905 2.083-.604.634-1.66 1.057-3.169 1.268-.121-.361-.258-.679-.408-.95.965-.151 1.645-.333 2.037-.545.454-.21.785-.481.998-.813.21-.303.314-.663.314-1.087 0-.482-.136-.905-.407-1.269-.331-.331-.8-.589-1.402-.77-.333.634-.649 1.117-.951 1.449-.242.332-.694.906-1.358 1.721.09.393.181.709.272.951l-1.042.362-.091-.498c-.423.361-.801.617-1.133.77-.361.15-.664.226-.905.226-.303 0-.574-.136-.814-.407-.243-.301-.362-.68-.362-1.132 0-.604.136-1.147.407-1.63.241-.453.603-.89 1.086-1.313.272-.241.725-.528 1.359-.86 0-.271.03-.799.09-1.585-.514.03-.921.045-1.222.045-.393 0-.711-.015-.951-.045l-.046-1.041c.725.091 1.494.135 2.31.135 0-.149.075-.738.227-1.766l1.177.183c-.151.542-.256 1.041-.316 1.493.242-.029.543-.075.906-.136.362-.061.573-.091.634-.091s.648-.15 1.766-.453l.046 1.041c-.967.243-2.145.439-3.532.591-.062.663-.092 1.086-.092 1.266.663-.151 1.284-.225 1.857-.225zm-2.672 3.893c-.061-.481-.136-1.252-.227-2.31-.573.424-1.041.86-1.403 1.313-.303.423-.452.875-.452 1.358 0 .241.044.438.136.588.09.092.195.137.316.137.363.001.907-.361 1.63-1.086zm.771-2.763c0 .483.029 1.088.09 1.811.604-.905 1.057-1.599 1.359-2.082-.574.06-1.058.151-1.449.271z"/>
+ <path id="english" d="M9.497 15.981h1.851l-3.084-8.949h-1.85l-3.081 8.949h1.85l.557-1.981h3.209l.548 1.981zm-3.489-3.377l1.331-3.782 1.344 3.782h-2.675z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-ltr.svg
new file mode 100644
index 00000000..47e71b39
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="layout-ltr">
+ <path id="text" d="M5 19v-14h6v8h8v6h-14z"/>
+ <path id="float" d="M13 5v6h6v-6h-6zm5 5h-4v-4h4v4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-rtl.svg
new file mode 100644
index 00000000..fe9ee617
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/layout-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="layout-rtl">
+ <path id="text" d="M5 19v-6h8v-8h6v14h-14z"/>
+ <path id="float" d="M5 5v6h6v-6h-6zm1 1h4v4h-4v-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/link.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/link.svg
new file mode 100644
index 00000000..dbae3414
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/link.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="link">
+ <path id="right" d="M19.188 12.001c0 1.1-.891 2.015-1.988 2.015l-4.195-.015c.538 1.088.963 1.999 1.997 1.999h3c1.656 0 2.998-2.343 2.998-4s-1.342-4-2.998-4h-3c-1.034 0-1.459.911-1.998 1.999l4.195-.015c1.098 0 1.989.917 1.989 2.017z"/>
+ <path id="center" d="M8 12c0 .535.42 1 .938 1h6.109c.518 0 .938-.465.938-1 0-.534-.42-1-.938-1h-6.109c-.518 0-.938.466-.938 1z"/>
+ <path id="left" d="M4.816 11.999c0-1.1.891-2.015 1.988-2.015l4.196.015c-.539-1.088-.964-1.999-1.998-1.999h-3c-1.656 0-2.998 2.343-2.998 4s1.342 4 2.998 4h3c1.034 0 1.459-.911 1.998-1.999l-4.195.015c-1.098 0-1.989-.917-1.989-2.017z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-ltr.svg
new file mode 100644
index 00000000..5a43f5ce
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-ltr.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bullet-list-ltr">
+ <path id="bottom_dot" d="M5 10h-1c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h1c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/>
+ <path id="middle_dot" d="M5 17h-1c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h1c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/>
+ <path id="top_dot" d="M5 3h-1c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h1c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/>
+ <path id="bottom_line" d="M20 17h-11c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h11c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/>
+ <path id="middle_line" d="M20 10h-11c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h11c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/>
+ <path id="top_line" d="M20 3h-11c-.552 0-1 .447-1 1v1c0 .553.448 1 1 1h11c.552 0 1-.447 1-1v-1c0-.553-.448-1-1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-rtl.svg
new file mode 100644
index 00000000..fb6e9569
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listBullet-rtl.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bullet-list-rtl">
+ <path id="bottom_dot_1_" d="M19 10h1c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-1c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/>
+ <path id="middle_dot_1_" d="M19 17h1c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-1c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/>
+ <path id="top_dot_1_" d="M19 3h1c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-1c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/>
+ <path id="bottom_line_7_" d="M4 17h11c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-11c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/>
+ <path id="middle_line_7_" d="M4 10h11c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-11c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/>
+ <path id="top_line_7_" d="M4 3h11c.552 0 1 .447 1 1v1c0 .553-.448 1-1 1h-11c-.552 0-1-.447-1-1v-1c0-.553.448-1 1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-ltr.svg
new file mode 100644
index 00000000..e929dae0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-ltr.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="number-list-ltr">
+ <path id="bottom_dot" d="M3 16v1h1.993l.03 1h-1.023v1h1v1h-2v1h2.023l.977-1.002v-1l-.955-.531.955-.5v-.969l-1.007-.998z"/>
+ <path id="middle_dot" d="M3 9v1h2.117l-2.117 2.187v1.811l3-.062v-.936h-2.118l2.118-2.188v-1.032l-.668-.78z"/>
+ <path id="top_dot" d="M4.993 2h-.648l-1.327 1.391.031.609h1.025l-.068 2h-1.006v1h3v-1h-1.037z"/>
+ <path id="bottom_line" d="M20.002 17h-11.002c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h11.002c.551 0 .998-.447.998-1v-1c0-.553-.447-1-.998-1z"/>
+ <path id="middle_line" d="M20.002 10h-11.002c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h11.002c.551 0 .998-.447.998-1v-1c0-.553-.447-1-.998-1z"/>
+ <path id="top_line" d="M20.002 3h-11.002c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h11.002c.551 0 .998-.447.998-1v-1c0-.553-.447-1-.998-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-rtl.svg
new file mode 100644
index 00000000..bbfa92f4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/listNumbered-rtl.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="number-list-rtl">
+ <path id="bottom_dot" d="M18 16v1h1.993l.03 1h-1.023v1h1v1h-2v1h2.023l.977-1.002v-1l-.956-.531.956-.5v-.969l-1.007-.998z"/>
+ <path id="middle_dot" d="M18 9v1h2.116l-2.116 2.187v1.811l3-.062v-.936h-2.118l2.118-2.188v-1.032l-.669-.78z"/>
+ <path id="top_dot" d="M19.993 2h-.648l-1.328 1.391.031.609h1.026l-.069 2h-1.005v1h3v-1h-1.038z"/>
+ <path id="bottom_line" d="M3.999 17h11.002c.552 0 .999.447.999 1v1c0 .553-.447 1-.999 1h-11.002c-.552 0-.999-.447-.999-1v-1c0-.553.447-1 .999-1z"/>
+ <path id="middle_line" d="M3.999 10h11.002c.552 0 .999.447.999 1v1c0 .553-.447 1-.999 1h-11.002c-.552 0-.999-.447-.999-1v-1c0-.553.447-1 .999-1z"/>
+ <path id="top_line" d="M3.999 3h11.002c.552 0 .999.447.999 1v1c0 .553-.447 1-.999 1h-11.002c-.552 0-.999-.447-.999-1v-1c0-.553.447-1 .999-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/lock.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/lock.svg
new file mode 100644
index 00000000..76328f37
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/lock.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="lock">
+ <path d="M12 6c-2.21 0-4 1.79-4 4v1H7v7h10v-7h-1v-1c0-2.21-1.79-4-4-4zm0 2c1.105 0 2 .895 2 2v1h-4v-1c0-1.105.895-2 2-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/menu.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/menu.svg
new file mode 100644
index 00000000..50ac8a39
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/menu.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="menu">
+ <path id="lines" d="M6 15h12c.553 0 1 .447 1 1v1c0 .553-.447 1-1 1h-12c-.553 0-1-.447-1-1v-1c0-.553.447-1 1-1zm-1-4v1c0 .553.447 1 1 1h12c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1h-12c-.553 0-1 .447-1 1zm0-5v1c0 .553.447 1 1 1h12c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1h-12c-.553 0-1 .447-1 1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-ltr.svg
new file mode 100644
index 00000000..51e6611a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="move-ltr">
+ <path id="arrow" d="M8.935 7.181l5.302 5.302-5.302 5.303 1.414 1.414 6.716-6.717-6.716-6.716z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-rtl.svg
new file mode 100644
index 00000000..bcee09d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="move-rtl">
+ <path id="arrow" d="M15.065 17.786l-5.302-5.303 5.302-5.302-1.414-1.414-6.716 6.716 6.716 6.717z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move.svg
new file mode 100644
index 00000000..9063bd48
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/move.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M20 11l-4-3v2h-3v-3h2l-3-4-3 4h2v3h-3v-2l-4 3 4 3v-2h3v3h-2l3 4 3-4h-2v-3h3v2z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-ltr.svg
new file mode 100644
index 00000000..dad5f51c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="line_return">
+ <path d="M17.8 5.7c-.5 0-.9.2-1.2.5s-.5.7-.5 1.2v4.3h-5.1v-4l-6 5.5 6 5.5v-4h8v-9h-1.2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-rtl.svg
new file mode 100644
index 00000000..fd758cc6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/newline-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="line_return">
+ <path d="M6.2 5.7c.5 0 .9.2 1.2.5.3.3.5.7.5 1.2v4.3H13v-4l6 5.5-6 5.5v-4H5v-9h1.2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-ltr.svg
new file mode 100644
index 00000000..601428e2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M16 14l2 2v-11h-4v2h2zm0 2l-7-7-2-2-1-1-1-1-3-3-1 1 2 2h-1v14h4v-2h-2v-10h1l2 2v10h4v-2h-2v-6l6 6h-1v2h3l4 4 1-1-4-4zm-5-9v-2h-4l2 2zm8-2v2h2v10h-2l2 2h2v-14z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-rtl.svg
new file mode 100644
index 00000000..31785a3c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/noWikiText-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g484">
+ <path d="M8 14l-2 2v-11h4v2h-2zm0 2l7-7 2-2 1-1 1-1 3-3 1 1-2 2h1v14h-4v-2h2v-10h-1l-2 2v10h-4v-2h2v-6l-6 6h1v2h-3l-4 4-1-1 4-4zm5-9v-2h4l-2 2zm-8-2v2h-2v10h2l-2 2h-2v-14z" id="path486"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-ltr.svg
new file mode 100644
index 00000000..344b7617
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-ltr.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="outdent-list-ltr">
+ <path id="arrow" d="M8 13h2v-3h-2.052l-.031-2.06-4.712 3.585 4.795 3.554z"/>
+ <path id="bottom_line" d="M20 17h-16c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h16c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1z"/>
+ <path id="middle_line" d="M20 10h-7c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h7c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1z"/>
+ <path id="top_line" d="M20 3h-16c-.553 0-1 .447-1 1v1c0 .553.447 1 1 1h16c.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-rtl.svg
new file mode 100644
index 00000000..31e92c51
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outdent-rtl.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="outdent-list-rtl">
+ <path id="arrow" d="M16 13h-2v-3h2.052l.031-2.06 4.712 3.585-4.795 3.554z"/>
+ <path id="bottom_line" d="M4 17h16c.553 0 1 .447 1 1v1c0 .553-.447 1-1 1h-16c-.553 0-1-.447-1-1v-1c0-.553.447-1 1-1z"/>
+ <path id="middle_line" d="M4 10h7c.553 0 1 .447 1 1v1c0 .553-.447 1-1 1h-7c-.553 0-1-.447-1-1v-1c0-.553.447-1 1-1z"/>
+ <path id="top_line" d="M4 3h16c.553 0 1 .447 1 1v1c0 .553-.447 1-1 1h-16c-.553 0-1-.447-1-1v-1c0-.553.447-1 1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-ltr.svg
new file mode 100644
index 00000000..9c0ea598
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="outline-ltr">
+ <path id="text" d="M5 13h14v6h-14v-6z"/>
+ <path id="float" d="M5 5v6h6v-6h-6zm5 5h-4v-4h4v4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-rtl.svg
new file mode 100644
index 00000000..2a3428e9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/outline-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="outline-rtl">
+ <path id="text" d="M19 19h-14v-6h14v6z"/>
+ <path id="float" d="M13 5v6h6v-6h-6zm1 1h4v4h-4v-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/picture.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/picture.svg
new file mode 100644
index 00000000..7400bca9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/picture.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="picture">
+ <path id="frame" d="M18 4h-12c-2-.007-3 .993-3 2.993l.014 9.007c-.014 2 .986 2.988 2.986 3h12c2-.012 2.994-1 3-3.006v-9.001c-.006-2-1-3-3-2.993zm1 13h-14v-11h14v11z"/>
+ <path id="mountains" d="M6 13.5l3.5-3.5 2.328 2.312-1.312 1.094.875 1.032 4.109-3.438 2.5 2v3h-12z"/>
+ <path id="sky" d="M6 12l3.516-4.156 3.046 3.172 2.938-2.016 2.5 2v-4h-12z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-ltr.svg
new file mode 100644
index 00000000..97b77bb4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18 9.9c-.7 0-1.4.3-1.8.9v-4.8h-4c.2-.4.4-.8.4-1.2 0-1.2-1-2.2-2.2-2.2-1.3-.1-2.3.9-2.3 2.2 0 .4.2.8.4 1.2h-4.4v3.6l.6-.1c1.4 0 2.5 1.1 2.5 2.5s-1.1 2.5-2.5 2.5c-.2 0-.4 0-.6-.1v3.6h4.9c-.5.4-.9 1-.9 1.8 0 1.2 1 2.2 2.3 2.2 1.2 0 2.2-1 2.2-2.2 0-.7-.3-1.4-.9-1.8h4.5v-4.5c.4.5 1 .9 1.8.9 1.2 0 2.2-1 2.2-2.2 0-1.3-1-2.3-2.2-2.3z" id="path542"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-rtl.svg
new file mode 100644
index 00000000..0ad5f375
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/puzzle-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M6.3 9.9c.7 0 1.4.3 1.8.9v-4.8h4c-.2-.4-.4-.8-.4-1.2 0-1.2 1-2.2 2.2-2.2 1.3-.1 2.3.9 2.3 2.2 0 .4-.2.8-.4 1.2h4.4v3.6l-.6-.1c-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5c.2 0 .4 0 .6-.1v3.6h-4.9c.5.4.9 1 .9 1.8 0 1.2-1 2.2-2.3 2.2-1.2 0-2.2-1-2.2-2.2 0-.7.3-1.4.9-1.8h-4.5v-4.5c-.4.5-1 .9-1.8.9-1.2 0-2.2-1-2.2-2.2 0-1.3 1-2.3 2.2-2.3z" id="path542"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-ltr.svg
new file mode 100644
index 00000000..dc1c06f0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M19.9 8.7c.3-.1.6-.3.8-.6s.3-.7.3-1.1v-1c-1.3.2-1.9.2-3.3.8-.9.5-1.6 1.1-2.2 1.8s-2.5 3.4-2.5 7.4v4h6c1.1 0 2-.9 2-2v-6h-4s.1-.9.8-1.8c.6-.7 1.3-1.2 2.1-1.5zm-14.4-.1c-.6.7-2.5 3.4-2.5 7.4v4h6c1.1 0 2-.9 2-2v-6h-4s.1-.9.8-1.8c.6-.7 1.3-1.2 2.1-1.5.3-.1.6-.3.8-.6s.3-.7.3-1.1v-1c-1.3.2-1.9.2-3.3.8-.8.5-1.6 1.1-2.2 1.8z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-rtl.svg
new file mode 100644
index 00000000..3a8b7012
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotes-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g552">
+ <path d="M4.1 8.7c-.3-.1-.6-.3-.8-.6-.2-.3-.3-.7-.3-1.1v-1c1.3.2 1.9.2 3.3.8.9.5 1.6 1.1 2.2 1.8.6.7 2.5 3.4 2.5 7.4v4h-6c-1.1 0-2-.9-2-2v-6h4s-.1-.9-.8-1.8c-.6-.7-1.3-1.2-2.1-1.5zm14.4-.1c.6.7 2.5 3.4 2.5 7.4v4h-6c-1.1 0-2-.9-2-2v-6h4s-.1-.9-.8-1.8c-.6-.7-1.3-1.2-2.1-1.5-.3-.1-.6-.3-.8-.6-.2-.3-.3-.7-.3-1.1v-1c1.3.2 1.9.2 3.3.8.8.5 1.6 1.1 2.2 1.8z" id="path554"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-ltr.svg
new file mode 100644
index 00000000..24fca8f5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M3.5 8.6c-.6.7-2.5 3.4-2.5 7.4v4h6c1.1 0 2-.9 2-2v-6h-4s.1-.9.8-1.8c.6-.7 1.3-1.2 2.1-1.5.3-.1.6-.3.8-.6.2-.3.3-.7.3-1.1v-1c-1.3.2-1.9.2-3.3.8-.8.5-1.6 1.1-2.2 1.8zm15.5-3.6v-4h-2v4h-4v2h4v4h2v-4h4v-2zm-4 7s.1-.9.8-1.8l.2-.2v-2h-1.9l-.6.6c-.6.7-2.5 3.4-2.5 7.4v4h6c1.1 0 2-.9 2-2v-6h-4z" id="path6"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-rtl.svg
new file mode 100644
index 00000000..736f2a6d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/quotesAdd-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M20.5 8.6c.6.7 2.5 3.4 2.5 7.4v4h-6c-1.1 0-2-.9-2-2v-6h4s-.1-.9-.8-1.8c-.6-.7-1.3-1.2-2.1-1.5-.3-.1-.6-.3-.8-.6-.2-.3-.3-.7-.3-1.1v-1c1.3.2 1.9.2 3.3.8.8.5 1.6 1.1 2.2 1.8zm-15.5-3.6v-4h2v4h4v2h-4v4h-2v-4h-4v-2zm4 7s-.1-.9-.8-1.8l-.2-.2v-2h1.9l.6.6c.6.7 2.5 3.4 2.5 7.4v4h-6c-1.1 0-2-.9-2-2v-6h4z" id="path6"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-ltr.svg
new file mode 100644
index 00000000..884d40df
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-ltr.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="create_redirect">
+ <g>
+ <path d="M17.7 2.4c-.3-.3-.7-.4-1.2-.4h-12.1v16.2c0 .5.1.8.4 1.1s.7.7 1.2.7h10.2c-.6-.2-1.2-.5-1.9-1-.4-.3-.8-.6-1.2-1l-.5-.6h-6.2v-1.4h5.4s-.4-1.5-.4-2h-5v-1h9v1c.4.1 1.1.1 1.5.1.4 0 .7 0 1.1-.1v-10.5c.1-.5-.1-.9-.3-1.1zm-5.2 1.6h3v4.5h-3v-4.5zm-6.1 0h4v1.6h-4v-1.6zm0 3h4v1.5h-4v-1.5zm0 3h9v1.5h-9v-1.5zm12.7 3.1l4.9 3.8-4.9 4.8v-2.2c-1.7 0-2.9-.2-4.3-1.2-1.2-.8-2.5-2.6-2.3-4.1 1.4 1 2.9 1.5 4.4 1.5.7 0 1.4-.1 2.1-.3l.1-2.3"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-rtl.svg
new file mode 100644
index 00000000..a07e8364
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/redirect-rtl.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="create_redirect">
+ <g id="g3264">
+ <path d="M6.3 2.4c.3-.3.7-.4 1.2-.4h12.1v16.2c0 .5-.1.8-.4 1.1-.3.3-.7.7-1.2.7h-10.2c.6-.2 1.2-.5 1.9-1 .4-.3.8-.6 1.2-1l.5-.6h6.2v-1.4h-5.4s.4-1.5.4-2h5v-1h-9v1c-.4.1-1.1.1-1.5.1-.4 0-.7 0-1.1-.1v-10.5c-.1-.5.1-.9.3-1.1zm5.2 1.6h-3v4.5h3v-4.5zm6.1 0h-4v1.6h4v-1.6zm0 3h-4v1.5h4v-1.5zm0 3h-9v1.5h9v-1.5z" id="path3266"/>
+ <path d="M4.9 13.1l-4.9 3.8 4.9 4.8v-2.2c1.7 0 2.9-.2 4.3-1.2 1.2-.8 2.5-2.6 2.3-4.1-1.4 1-2.9 1.5-4.4 1.5-.7 0-1.4-.1-2.1-.3l-.1-2.3" id="path3268"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/regular-expression.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/regular-expression.svg
new file mode 100644
index 00000000..7b672618
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/regular-expression.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="regular-expression">
+ <path id="left-bracket" d="m 3,12.044797 c -5e-7,-0.989171 0.150394,-1.914889 0.451184,-2.7771612 C 3.7558785,8.4053812 4.1933899,7.6495032 4.7637193,7 L 6.2286026,7 C 5.6778034,7.7204251 5.261777,8.511764 4.9805221,9.3740188 4.6992623,10.236291 4.5586337,11.122815 4.5586357,12.033598 c -2e-6,0.914522 0.1425798,1.799179 0.427746,2.653974 C 5.2754491,15.538635 5.6856161,16.309444 6.2168835,17 L 4.7637193,17 C 4.1894835,16.365435 3.7519721,15.624488 3.451184,14.777158 3.150394,13.929828 3,13.019042 3,12.044797" />
+ <path id="dot" d="m 10,16 c 0,0.552285 -0.4477153,1 -1,1 -0.5522847,0 -1,-0.447715 -1,-1 0,-0.552285 0.4477153,-1 1,-1 0.5522847,0 1,0.447715 1,1 z" />
+ <path id="star" d="m 14.250652,7.0127142 -0.240235,2.15625 2.185547,-0.609375 0.193359,1.4765618 -1.992187,0.140625 1.306641,1.740234 -1.330079,0.708985 -0.914062,-1.833985 -0.802734,1.822266 -1.382813,-0.697266 1.294922,-1.740234 -1.980469,-0.152343 0.228516,-1.4648438 2.138672,0.609375 -0.240235,-2.15625 1.535157,0" />
+ <path id="right-bracket" d="m 21,12.044797 c -3e-6,0.981711 -0.152351,1.896229 -0.457043,2.743558 C 20.241767,15.635686 19.806209,16.3729 19.235883,17 l -1.453164,0 c 0.527356,-0.686824 0.93557,-1.455766 1.224642,-2.306829 0.289069,-0.854795 0.433604,-1.741318 0.433606,-2.659573 -2e-6,-0.910783 -0.140631,-1.797307 -0.421886,-2.6595792 C 18.737821,8.511764 18.321795,7.7204251 17.771,7 l 1.464883,0 c 0.574232,0.653236 1.011744,1.4128466 1.312536,2.2788341 0.300785,0.8622719 0.45118,1.7842569 0.451183,2.7659629" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/remove.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/remove.svg
new file mode 100644
index 00000000..6ad79174
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/remove.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="remove">
+ <path id="trash-can" d="M12 10h-1v6h1v-6zm-2 0h-1v6h1v-6zm4 0h-1v6h1v-6zm0-4v-1h-5v1h-3v3h1v7.966l1 1.031v-.074.077h6.984l.016-.018v.015l1-1.031v-7.966h1v-3h-3zm1 11h-7v-8h7v8zm1-9h-9v-1h9v1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/search.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/search.svg
new file mode 100644
index 00000000..208d44b3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/search.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="search">
+ <path id="search" d="M16.021 15.96l-2.374-2.375-.169-.099c.403-.566.643-1.26.643-2.009-.001-1.92-1.558-3.477-3.477-3.477-1.921 0-3.478 1.557-3.478 3.478 0 1.92 1.557 3.477 3.478 3.477.749 0 1.442-.239 2.01-.643l.098.169 2.375 2.374c.19.189.543.143.79-.104s.293-.601.104-.791zm-5.377-2.27c-1.221 0-2.213-.991-2.213-2.213 0-1.221.992-2.213 2.213-2.213 1.222 0 2.213.992 2.213 2.213-.001 1.222-.992 2.213-2.213 2.213z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/secure-link.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/secure-link.svg
new file mode 100644
index 00000000..a9c7d276
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/secure-link.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="secure">
+ <path id="lock" d="M8 5h.019v-.997c.001-.057.004-1.409-.832-2.255-.434-.438-.998-.66-1.679-.66s-1.245.222-1.678.659c-.837.847-.833 2.199-.832 2.251v1.002h.002c-.553 0-1 .447-1 1v3c0 .553.447 1 1 1h5c.553 0 1-.447 1-1v-3c0-.553-.447-1-1-1zm-4.002 0v-1.007c0-.01.005-.999.543-1.543.482-.485 1.449-.487 1.932-.002.544.546.546 1.536.546 1.55v1.002h-3.021z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/settings.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/settings.svg
new file mode 100644
index 00000000..9fa0a4b3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/settings.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0, 0, 24, 24">
+ <g id="settings">
+ <path id="gear" d="M3 4h3v2h-3zM12 4h9v2h-9zM8 3h2c.552 0 1 .448 1 1v2c0 .552-.448 1-1 1h-2c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1zM3 11h9v2h-9zM18 11h3v2h-3zM14 10h2c.552 0 1 .448 1 1v2c0 .552-.448 1-1 1h-2c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1zM3 18h6v2h-6zM15 18h6v2h-6zM11 17h2c.552 0 1 .448 1 1v2c0 .552-.448 1-1 1h-2c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-ltr.svg
new file mode 100644
index 00000000..e8b427b1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+ <g id="down">
+ <path id="arrow" d="M22 3l-3.5 6L15 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-rtl.svg
new file mode 100644
index 00000000..e5e95196
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/smaller-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+ <g id="down">
+ <path id="arrow" d="M9 3L5.5 9 2 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/specialCharacter.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/specialCharacter.svg
new file mode 100644
index 00000000..4d601281
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/specialCharacter.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="special-character">
+ <path id="omega" d="M12 6.708c-.794 0-1.368.103-1.894.31-.525.207-.944.496-1.255.867-.311.366-.531.808-.66 1.327-.128.513-.192 1.08-.192 1.699 0 .513.058 1 .174 1.46.122.46.311.87.568 1.23.629.863 1.155 1.139 2.011 1.363l.247 3.035h-5v-3h.605l.531 1.354.394.053.605.044.751.035.456.009h.66l-.092-.894c-.629-.094-.811-.268-1.336-.522-.525-.26-.98-.59-1.365-.991-.379-.401-.675-.867-.889-1.398-.214-.537-.321-1.13-.321-1.779 0-.82.131-1.537.394-2.15.269-.619.656-1.133 1.163-1.54.507-.407 1.133-.711 1.878-.912.745-.206 1.6-.31 2.565-.31.959 0 1.811.103 2.556.31.751.201 1.38.504 1.887.912.507.407.892.92 1.154 1.54.269.614.403 1.33.403 2.15 0 .649-.107 1.242-.321 1.779-.214.531-.513.997-.898 1.398-.379.401-.831.732-1.356.991-.525.254-.707.428-1.336.522l-.092.894h.66l.447-.009.751-.035.605-.044.403-.053.531-1.354h.605v3h-5l.247-3.035c1.066-.11 1.337-.696 2.002-1.363.263-.36.452-.77.568-1.23.122-.46.183-.947.183-1.46 0-.619-.064-1.186-.192-1.699-.128-.519-.348-.962-.66-1.327-.311-.372-.73-.661-1.255-.867-.525-.206-1.1-.31-1.894-.31"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/star.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/star.svg
new file mode 100644
index 00000000..ea8c26c6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/star.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5 1.9-3.6m0-3.4l-2.8 5.6-6.2.9 4.5 4.4-1.1 6.1 5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9-2.7-5.5z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-a.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-a.svg
new file mode 100644
index 00000000..480189f5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-a.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="strikethrough-a">
+ <path id="strikethrough" d="M6 11h12v1h-12v-1z"/>
+ <path id="a" d="M12.666 6h-1.372l-4.48 12h1.705l1.494-4h3.999l1.508 4h1.666l-4.52-12zm-2.28 7l1.617-4.333 1.634 4.333h-3.251z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-s.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-s.svg
new file mode 100644
index 00000000..d57b652f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-s.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="strikethrough-s">
+ <path id="strikethrough" d="M6 12h12v1h-12v-1z"/>
+ <path id="s" d="M12.094 6c-1.133 0-2.076.287-2.75.9-.67.613-1 1.49-1 2.52 0 .889.221 1.602.719 2.13.498.528 1.279.91 2.312 1.14l.812.182v-.03c.656.147 1.128.375 1.375.63.252.256.375.607.375 1.11 0 .573-.172.97-.531 1.26-.358.291-.894.45-1.625.45-.477 0-.969-.074-1.469-.24-.502-.166-1.031-.417-1.562-.75l-.375-.238v2.158l.156.062c.58.237 1.143.417 1.688.54.549.121 1.07.18 1.562.18 1.286 0 2.297-.293 3-.9.709-.605 1.062-1.486 1.062-2.608 0-.943-.256-1.726-.781-2.312-.521-.592-1.305-1-2.344-1.229l-.812-.181c-.716-.148-1.204-.352-1.406-.539-.205-.203-.312-.485-.312-.935 0-.533.162-.899.5-1.17.342-.271.836-.42 1.531-.42.395 0 .818.052 1.25.181.433.127.908.333 1.406.6l.375.18v-2.041s-1.188-.383-1.688-.479c-.499-.098-.984-.151-1.468-.151z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-y.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-y.svg
new file mode 100644
index 00000000..8409dc15
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/strikethrough-y.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="strikethrough-y">
+ <path id="strikethrough" d="M6 11h12v1h-12v-1z"/>
+ <path id="a" d="M7 6h1.724l3.288 4.935 3.264-4.935h1.724l-4.194 6.285v5.715h-1.612v-5.715l-4.194-6.285"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-ltr.svg
new file mode 100644
index 00000000..b7507daf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-ltr.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+ <path d="M18 13l-1 1v3l1 1h-1l-.527-.46L16 18h-1l1-1v-3l-1-1h1l.485.497L17 13z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-rtl.svg
new file mode 100644
index 00000000..9fe5325f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/subscript-rtl.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+ <path d="M8 13l1 1v3l-1 1h1l.527-.46L10 18h1l-1-1v-3l1-1h-1l-.485.497L9 13z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-ltr.svg
new file mode 100644
index 00000000..39f30a76
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-ltr.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+ <path d="M18 7l-1 1v3l1 1h-1l-.527-.46L16 12h-1l1-1V8l-1-1h1l.485.497L17 7z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-rtl.svg
new file mode 100644
index 00000000..eabab21c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/superscript-rtl.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+ <path d="M8 7l1 1v3l-1 1h1l.527-.46L10 12h1l-1-1V8l1-1h-1l-.485.497L9 7z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-caption.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-caption.svg
new file mode 100644
index 00000000..15bb06a6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-caption.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-caption">
+ <path id="caption" d="M6 6h12v3H6z"/>
+ <path id="table" d="M4 10v7h16v-7H4zm1 1h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2zM5 14h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-ltr.svg
new file mode 100644
index 00000000..798ee4a1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-ltr.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-column-ltr">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 5,5 2,0 0,14 -2,0 z"
+ id="column" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-rtl.svg
new file mode 100644
index 00000000..dfa33a08
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-column-rtl.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-column-rtl">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 17,5 2,0 0,14 -2,0 z"
+ id="column" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-after.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-after.svg
new file mode 100644
index 00000000..91d06644
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-after.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-row-after">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 5,17 14,0 0,2 -14,0 z"
+ id="row" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-before.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-before.svg
new file mode 100644
index 00000000..4b71f2a8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-insert-row-before.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-row-before">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 5,5 14,0 0,2 -14,0 z"
+ id="row" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-merge-cells.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-merge-cells.svg
new file mode 100644
index 00000000..6a8b77d8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table-merge-cells.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-merge-cells">
+ <g id="merge-cell-left">
+ <path id="cell-border" d="m 4,7 0,9 7,0 0,-3 -1,0.834 L 10,15 5,15 5,8 10,8 10,9.167 11,10 11,7 z" />
+ <path id="arrow" d="m 8,9 0,2 -2,0 0,1 2,0 0,2 3,-2.5 z" />
+ </g>
+ <use id="merge-cell-right" xlink:href="#merge-cell-left" transform="matrix(-1,0,0,1,24,0)" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table.svg
new file mode 100644
index 00000000..1ba8c440
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/table.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+
+ <g id="table-insert">
+ <path id="table" d="M4 5v13h16v-13zm2 2h5v4h-5zm7 0h5v4h-5zm-7 5h5v4h-5zm7 0h5v4h-5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/tag.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/tag.svg
new file mode 100644
index 00000000..534824c8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/tag.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="tag">
+ <path d="M18.748 11.717c.389.389.389 1.025 0 1.414l-4.949 4.95c-.389.389-1.025.389-1.414 0l-6.01-6.01c-.389-.389-.707-1.157-.707-1.707l-.001-4.364c0-.55.45-1 1-1h4.364c.55 0 1.318.318 1.707.707l6.01 6.01zm-10.644-4.261c-.579.576-.578 1.514-.001 2.093.578.577 1.516.577 2.095.001.576-.578.576-1.517 0-2.095-.581-.576-1.518-.577-2.094.001z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-ltr.svg
new file mode 100644
index 00000000..6b594b29
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M24 6h-4v-4h-2v4h-4v2h4v4h2v-4h4z"/>
+ </g>
+ <path d="M19 13v7h-16c-1.1 0-2-.9-2-2v-11h12v-1h-13v12c0 1.7 1.3 3 3 3h17v-8h-1z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-rtl.svg
new file mode 100644
index 00000000..36b25a3e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/templateAdd-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g690">
+ <path d="M0 6h4v-4h2v4h4v2h-4v4h-2v-4h-4z" id="path692"/>
+ </g>
+ <path d="M5 13v7h16c1.1 0 2-.9 2-2v-11h-12v-1h13v12c0 1.7-1.3 3-3 3h-17v-8h1z" id="path694"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-lefttoright.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-lefttoright.svg
new file mode 100644
index 00000000..62526a03
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-lefttoright.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="text-dir-ltr">
+ <path d="M7 7h-2v-1h2l.469.5.531-.5h2v1h-2v10h2v1h-2l-.5-.531-.5.531h-2v-1h2zM13.976 16v-2h-2.976v-4h2.976v-1.956l6.024 3.978z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-righttoleft.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-righttoleft.svg
new file mode 100644
index 00000000..913bbfd6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-dir-righttoleft.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="text-dir-rtl">
+ <path d="M17 17h2v1h-2l-.469-.5-.531.5h-2v-1h2v-10h-2v-1h2l.5.531.5-.531h2v1h-2zM10.024 8v2h2.976v4h-2.976v1.956l-6.024-3.978z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-style.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-style.svg
new file mode 100644
index 00000000..0198c355
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/text-style.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="text-style">
+ <path id="a" d="M15.296 18h2.789l-1.14-12h-2.789l-8.156 12h2.789l2.039-3h4.183l.285 3zm-3.109-5l2.311-3.4.323 3.4h-2.634z"/>
+ <path id="underline" d="M6 19h12v1h-12v-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-ltr.svg
new file mode 100644
index 00000000..7740e43e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M11.1 13.1c-1.8-2.1-2.7-4.3-3-5.1h4.7l.7-2h-5.5v-3h-2v3h-5v2h5c-.2.9-1.3 4.8-5.1 7.6l1.2 1.6c2.7-2 4.3-4.5 5.1-6.4.7 1.3 1.7 3 3.2 4.5l.7-2.2zm1.4 6.9l1.3-4h5.3l1.3 4h2.2l-4.6-14h-3l-4.7 14h2.2zm4-12l2 6h-4l2-6z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-rtl.svg
new file mode 100644
index 00000000..c78e6222
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/translation-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12.4 13.1c1.8-2.1 2.7-4.3 3-5.1h-4.7l-.7-2h5.5v-3h2v3h5v2h-5c.2.9 1.3 4.8 5.1 7.6l-1.2 1.6c-2.7-2-4.3-4.5-5.1-6.4-.7 1.3-1.7 3-3.2 4.5l-.7-2.2zm-1.4 6.9l-1.3-4h-5.3l-1.3 4h-2.2l4.6-14h3l4.7 14h-2.2zm-4-12l-2 6h4l-2-6z" id="path704"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trash.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trash.svg
new file mode 100644
index 00000000..f5914312
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trash.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2h-12zm1 1h10l-1 11h-8z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-ltr.svg
new file mode 100644
index 00000000..0731f056
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M20.5 20.5l-15.5-15.5-1 1 3 3 1 11h8l.2-1.8 3.3 3.3zm-3.5-11.5h-6l5.5 5.5zm1-1c0-1.1-.9-2-2-2h-2l-1-1h-2l-1 1h-2l2 2h8z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-rtl.svg
new file mode 100644
index 00000000..2a92cbef
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/trashUndo-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g714">
+ <path d="M4 20.5l15.5-15.5 1 1-3 3-1 11h-8l-.2-1.8-3.3 3.3zm3.5-11.5h6l-5.5 5.5zm-1-1c0-1.1.9-2 2-2h2l1-1h2l1 1h2l-2 2h-8z" id="path716"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-ltr.svg
new file mode 100644
index 00000000..66c024a9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 9v-2s0-5-4.5-5-4.5 5-4.5 5h2s0-3 2.5-3 2.5 3 2.5 3v2h-3v7c0 1.7 1.3 3 3 3h10v-10z" id="path726"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-rtl.svg
new file mode 100644
index 00000000..07cecbfe
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unLock-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M11 9v-2s0-5 4.5-5 4.5 5 4.5 5h-2s0-3-2.5-3-2.5 3-2.5 3v2h3v7c0 1.7-1.3 3-3 3h-10v-10z" id="path726"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unStar.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unStar.svg
new file mode 100644
index 00000000..724d1901
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/unStar.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-a.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-a.svg
new file mode 100644
index 00000000..dd6dde36
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-a.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="underline-a">
+ <path id="a" d="M14.424 16h2.076l-3.463-10h-2.077l-3.46 10h2.077l.627-2h3.604l.616 2zm-3.921-3.623l1.496-4.379 1.511 4.379h-3z"/>
+ <path id="underline" d="M7 17h10v1h-10v-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-u.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-u.svg
new file mode 100644
index 00000000..fbd7c147
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/underline-u.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="underline-u">
+ <path id="u" d="M8 6h2v5.959c-.104 1.707.695 2.002 2 2.041 1.777.062 2.002-.879 2-2.041v-5.959h2v6.123c0 1.279-.338 2.245-1.016 2.898-.672.651-1.666.979-2.98.979-1.32 0-2.319-.326-2.996-.979-.672-.653-1.008-1.619-1.008-2.898v-6.123"/>
+ <path id="underline" d="M7 17h10v1h-10v-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/upTriangle.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/upTriangle.svg
new file mode 100644
index 00000000..9e5e72f6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/upTriangle.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 8l8 10h-16z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/wikiText.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/wikiText.svg
new file mode 100644
index 00000000..eebd9b1a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/wikiText.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M23 5h-4v2h2v10h-2v2h4z"/>
+ </g>
+ <g>
+ <path d="M18 5h-4v2h2v10h-2v2h4z"/>
+ </g>
+ <g>
+ <path d="M2 5h4v2h-2v10h2v2h-4z"/>
+ </g>
+ <g>
+ <path d="M7 5h4v2h-2v10h2v2h-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/icons/window.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/window.svg
new file mode 100644
index 00000000..cd3b76c2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/icons/window.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="window">
+ <path id="title" d="M7 10h10v1h-10z"/>
+ <path id="frame" d="M16 19h-8c-2.206 0-4-1.794-4-4v-6c0-2.206 1.794-4 4-4h8c2.206 0 4 1.794 4 4v6c0 2.206-1.794 4-4 4zm-8-12c-1.103 0-2 .897-2 2v6c0 1.103.897 2 2 2h8c1.103 0 2-.897 2-2v-6c0-1.103-.897-2-2-2h-8z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/alert.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/alert.svg
new file mode 100644
index 00000000..d9dc6a87
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/alert.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="alert">
+ <path d="M6 12c-3.314 0-6-2.686-6-6s2.686-6 6-6 6 2.686 6 6-2.686 6-6 6zm-1-5h2v-5h-2zm0 3h2v-2h-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-down.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-down.svg
new file mode 100644
index 00000000..bfa8ef0b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-down.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="down">
+ <path id="arrow" d="M2 3l3.5 6 3.5-6z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-ltr.svg
new file mode 100644
index 00000000..aeca27a9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="ltr">
+ <path id="arrow" d="M3 9v-7l6 3.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-rtl.svg
new file mode 100644
index 00000000..eba00995
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="rtl">
+ <path id="arrow" d="M3 5.5l6 3.5v-7z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-up.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-up.svg
new file mode 100644
index 00000000..4b01bb02
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/arrow-up.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="up">
+ <path id="arrow" d="M5.5 2l-3.5 6h7z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/required.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/required.svg
new file mode 100644
index 00000000..969fa2d8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/required.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="required">
+ <path d="M5 1h2v10h-2zM9.83 2.634l1 1.732-8.66 5-1-1.732zM1.17 4.366l1-1.732 8.66 5-1 1.732z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-ltr.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-ltr.svg
new file mode 100644
index 00000000..266349ed
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="search">
+ <path id="path3051" d="M10.369 9.474l-2.374-2.375-.169-.099c.403-.566.643-1.26.643-2.009-.001-1.92-1.558-3.477-3.477-3.477-1.921 0-3.478 1.557-3.478 3.478 0 1.92 1.557 3.477 3.478 3.477.749 0 1.442-.239 2.01-.643l.098.169 2.375 2.374c.19.189.543.143.79-.104s.293-.601.104-.791zm-5.377-2.27c-1.221 0-2.213-.991-2.213-2.213 0-1.221.992-2.213 2.213-2.213 1.222 0 2.213.992 2.213 2.213-.001 1.222-.992 2.213-2.213 2.213z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-rtl.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-rtl.svg
new file mode 100644
index 00000000..5368fd7c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/indicators/search-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="search">
+ <path id="path3051" d="M1.631 9.474l2.374-2.375.169-.099c-.403-.566-.643-1.26-.643-2.009.001-1.92 1.558-3.477 3.477-3.477 1.921 0 3.478 1.557 3.478 3.478 0 1.92-1.557 3.477-3.478 3.477-.749 0-1.442-.239-2.01-.643l-.098.169-2.375 2.374c-.19.189-.543.143-.79-.104s-.293-.601-.104-.791zm5.377-2.27c1.221 0 2.213-.991 2.213-2.213 0-1.221-.992-2.213-2.213-2.213-1.222 0-2.213.992-2.213 2.213.001 1.222.992 2.213 2.213 2.213z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/textures/pending.gif b/vendor/oojs/oojs-ui/src/themes/apex/images/textures/pending.gif
new file mode 100644
index 00000000..1194eed2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/textures/pending.gif
Binary files differ
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/textures/transparency.svg b/vendor/oojs/oojs-ui/src/themes/apex/images/textures/transparency.svg
new file mode 100644
index 00000000..63a0b57c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/textures/transparency.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="16" height="16" viewBox="0, 0, 16, 16">
+ <g id="transparency">
+ <path d="M0,0 L8,0 L8,8 L0,8 z" fill="#CCCCCC"/>
+ <path d="M8,8 L16,8 L16,16 L8,16 z" fill="#CCCCCC"/>
+ <path d="M8,0 L16,0 L16,8 L8,8 z" fill="#FFFFFF"/>
+ <path d="M0,8 L8,8 L8,16 L0,16 z" fill="#FFFFFF"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/images/toolbar-shadow.png b/vendor/oojs/oojs-ui/src/themes/apex/images/toolbar-shadow.png
new file mode 100644
index 00000000..97e8d13d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/images/toolbar-shadow.png
Binary files differ
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/indicators.json b/vendor/oojs/oojs-ui/src/themes/apex/indicators.json
new file mode 100644
index 00000000..0a9d1d26
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/indicators.json
@@ -0,0 +1,22 @@
+{
+ "prefix": "oo-ui-indicator",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "alert": { "file": "images/indicators/alert.svg" },
+ "up": { "file": "images/indicators/arrow-up.svg" },
+ "down": { "file": "images/indicators/arrow-down.svg" },
+ "next": { "file": {
+ "ltr": "images/indicators/arrow-ltr.svg",
+ "rtl": "images/indicators/arrow-rtl.svg"
+ } },
+ "previous": { "file": {
+ "ltr": "images/indicators/arrow-rtl.svg",
+ "rtl": "images/indicators/arrow-ltr.svg"
+ } },
+ "required": { "file": "images/indicators/required.svg" },
+ "search": { "file": {
+ "ltr": "images/indicators/search-ltr.svg",
+ "rtl": "images/indicators/search-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/layouts.less b/vendor/oojs/oojs-ui/src/themes/apex/layouts.less
new file mode 100644
index 00000000..b86aa010
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/layouts.less
@@ -0,0 +1,135 @@
+@import 'common';
+
+.theme-oo-ui-layout () {}
+
+.theme-oo-ui-bookletLayout () {
+ &-stackLayout {
+ > .oo-ui-panelLayout {
+ padding: 1.5em;
+ }
+ }
+
+ &-outlinePanel {
+ border-right: 1px solid #ddd;
+
+ > .oo-ui-outlineControlsWidget {
+ box-shadow: 0 0 0.25em rgba(0,0,0,0.25);
+ }
+ }
+}
+
+.theme-oo-ui-indexLayout () {
+ &-stackLayout {
+ > .oo-ui-panelLayout {
+ padding: 1.5em;
+ }
+ }
+}
+
+.theme-oo-ui-fieldLayout () {
+ margin-bottom: 1em;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ &.oo-ui-fieldLayout-align-left,
+ &.oo-ui-fieldLayout-align-right {
+ &.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding-top: 0.5em;
+ margin-right: 5%;
+ width: 35%;
+ }
+
+ > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+ width: 60%;
+ }
+ }
+
+ &.oo-ui-fieldLayout-align-inline {
+ &.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding: 0.5em;
+ }
+
+ > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+ padding: 0.5em 0;
+ }
+ }
+
+ &.oo-ui-fieldLayout-align-top {
+ &.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding: 0.5em 0;
+ }
+ }
+
+ > .oo-ui-popupButtonWidget {
+ .oo-ui-inline-spacing(0);
+ margin-top: 0.25em;
+ }
+
+ &-disabled .oo-ui-labelElement-label {
+ color: #ccc;
+ }
+}
+
+.theme-oo-ui-actionFieldLayout () {}
+
+.theme-oo-ui-fieldsetLayout () {
+ margin: 0;
+ padding: 0;
+ border: none;
+
+ + .oo-ui-fieldsetLayout,
+ + .oo-ui-formLayout {
+ margin-top: 2em;
+ }
+
+ > .oo-ui-labelElement-label {
+ font-size: 1.1em;
+ margin-bottom: 0.5em;
+ padding: 0.25em 0;
+ font-weight: bold;
+ }
+
+ &.oo-ui-iconElement > .oo-ui-labelElement-label {
+ padding-left: 2em;
+ line-height: 1.8em;
+ }
+
+ &.oo-ui-iconElement > .oo-ui-iconElement-icon {
+ left: 0;
+ top: 0.25em;
+ width: @icon-size;
+ height: @icon-size;
+ }
+
+ > .oo-ui-popupButtonWidget {
+ .oo-ui-inline-spacing(0);
+ }
+}
+
+.theme-oo-ui-formLayout () {
+ + .oo-ui-fieldsetLayout,
+ + .oo-ui-formLayout {
+ margin-top: 2em;
+ }
+}
+
+.theme-oo-ui-menuLayout () {}
+
+.theme-oo-ui-panelLayout () {
+ &-padded {
+ padding: 1.25em;
+ }
+
+ &-framed {
+ border-radius: 0.5em;
+ box-shadow: 0 0.25em 1em rgba(0, 0, 0, 0.25);
+ }
+}
+
+.theme-oo-ui-cardLayout () {}
+
+.theme-oo-ui-pageLayout () {}
+
+.theme-oo-ui-stackLayout () {}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/textures.json b/vendor/oojs/oojs-ui/src/themes/apex/textures.json
new file mode 100644
index 00000000..e90730ab
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/textures.json
@@ -0,0 +1,8 @@
+{
+ "prefix": "oo-ui-texture",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "pending": { "file": "images/textures/pending.gif" },
+ "transparency": { "file": "images/textures/transparency.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/tools.less b/vendor/oojs/oojs-ui/src/themes/apex/tools.less
new file mode 100644
index 00000000..913bb516
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/tools.less
@@ -0,0 +1,422 @@
+@import 'common';
+
+.theme-oo-ui-toolbar () {
+ &-bar {
+ border-bottom: 1px solid #ccc;
+ .oo-ui-vertical-gradient(#fff, #F1F7FB);
+
+ .oo-ui-toolbar-bar {
+ border: none;
+ background: none;
+ }
+ }
+
+ &-actions {
+ > .oo-ui-buttonElement {
+ margin-top: 0.4em;
+ margin-bottom: 0.4em;
+ }
+
+ > .oo-ui-buttonElement:last-child {
+ margin-right: 0.5em;
+ }
+ }
+
+ &-shadow {
+ .oo-ui-background-image('@{oo-ui-default-image-path}/toolbar-shadow.png');
+ bottom: -9px;
+ height: 9px;
+ opacity: 0.5;
+ .oo-ui-transition(opacity 500ms ease-in-out);
+ }
+}
+
+.theme-oo-ui-tool () {}
+
+.theme-oo-ui-popupTool () {
+ .oo-ui-popupWidget {
+ /* @noflip */
+ margin-left: 1.25em;
+ }
+}
+
+.theme-oo-ui-toolGroupTool () {
+ > .oo-ui-popupToolGroup {
+ border: 0;
+ border-radius: 0;
+ margin: 0;
+ }
+
+ // Like .oo-ui-tool in barToolGroup
+ &:first-child > .oo-ui-popupToolGroup {
+ border-top-left-radius: 0.3125em;
+ border-bottom-left-radius: 0.3125em;
+ }
+
+ &:last-child > .oo-ui-popupToolGroup {
+ border-top-right-radius: 0.3125em;
+ border-bottom-right-radius: 0.3125em;
+ }
+
+ // Like .oo-ui-tool-link in barToolGroup
+ > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle {
+ height: 1.875em;
+ padding: 0.3125em;
+
+ .oo-ui-iconElement-icon {
+ height: 1.875em;
+ width: 1.875em;
+ }
+ }
+
+ > .oo-ui-popupToolGroup.oo-ui-labelElement > .oo-ui-popupToolGroup-handle {
+ .oo-ui-labelElement-label {
+ line-height: 2.1em; // 0.5em less
+ }
+ }
+}
+
+.theme-oo-ui-toolGroup () {
+ margin: 0.375em;
+ border-radius: 0.3125em;
+ border: 1px solid transparent;
+ .oo-ui-transition(border-color 300ms ease-in-out);
+
+ .oo-ui-toolbar-narrow & {
+ + .oo-ui-toolGroup {
+ margin-left: 0;
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover {
+ border-color: rgba(0,0,0,0.1);
+ }
+
+ .oo-ui-tool-link .oo-ui-tool-title {
+ color: #000;
+ }
+ }
+}
+
+.theme-oo-ui-barToolGroup () {
+ > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ margin: -1px 0 -1px -1px;
+ border: 1px solid transparent;
+
+ &:first-child {
+ border-top-left-radius: 0.3125em;
+ border-bottom-left-radius: 0.3125em;
+ }
+
+ &:last-child {
+ margin-right: -1px;
+ border-top-right-radius: 0.3125em;
+ border-bottom-right-radius: 0.3125em;
+ }
+
+ > .oo-ui-tool-link {
+ height: 1.875em;
+ padding: 0.3125em;
+
+ .oo-ui-iconElement-icon {
+ height: 1.875em;
+ width: 1.875em;
+ }
+
+ .oo-ui-tool-title {
+ line-height: 2.1em; // 0.5em less
+ }
+ }
+ }
+
+ &.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ &.oo-ui-widget-enabled:hover {
+ border-color: rgba(0,0,0,0.2);
+ }
+
+ &.oo-ui-tool-active {
+ &.oo-ui-widget-enabled {
+ border-color: rgba(0,0,0,0.2);
+ box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
+ .oo-ui-vertical-gradient(#F1F7FB, #fff);
+ }
+
+ &.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
+ border-left-color: rgba(0,0,0,0.1);
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-tool-link {
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
+ opacity: 1;
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ > .oo-ui-tool-link {
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+ }
+}
+
+.theme-oo-ui-popupToolGroup () {
+ height: 2.5em;
+ min-width: 2.5em;
+
+ .oo-ui-toolbar-narrow & {
+ min-width: 1.875em;
+ }
+
+ &.oo-ui-iconElement {
+ min-width: 3.125em;
+
+ .oo-ui-toolbar-narrow & {
+ min-width: 2.5em;
+ }
+ }
+
+ &.oo-ui-indicatorElement.oo-ui-iconElement {
+ min-width: 4.375em;
+
+ .oo-ui-toolbar-narrow & {
+ min-width: 3.75em;
+ }
+ }
+
+ &.oo-ui-labelElement {
+ .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+ line-height: 2.6em;
+ margin: 0 1em;
+
+ .oo-ui-toolbar-narrow & {
+ margin: 0 0.5em;
+ }
+ }
+
+ &.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+ margin-left: 3em;
+
+ .oo-ui-toolbar-narrow & {
+ margin-left: 2.5em;
+ }
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+ margin-right: 2.25em;
+
+ .oo-ui-toolbar-narrow & {
+ margin-right: 1.75em;
+ }
+ }
+ }
+
+ &-handle {
+ .oo-ui-indicatorElement-indicator {
+ width: 0.9375em;
+ height: 0.9375em;
+ margin: 0.78125em;
+ top: 0;
+ right: 0;
+
+ .oo-ui-toolbar-narrow & {
+ right: -0.3125em;
+ }
+ }
+ .oo-ui-iconElement-icon {
+ width: 1.875em;
+ height: 1.875em;
+ margin: 0.3125em;
+ top: 0;
+ left: 0.3125em;
+
+ .oo-ui-toolbar-narrow & {
+ left: 0;
+ }
+ }
+ }
+
+ &-header {
+ line-height: 2.6em;
+ margin: 0 0.6em;
+ font-weight: bold;
+ }
+
+ &-active.oo-ui-widget-enabled {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
+
+ .oo-ui-vertical-gradient(#F1F7FB, #fff);
+ }
+
+ .oo-ui-toolGroup-tools {
+ top: 2.5em;
+ margin: 0 -1px;
+ border: 1px solid #ccc;
+ background-color: white;
+ box-shadow: 0 0.3125em 1.25em rgba(0,0,0,0.25);
+ }
+
+ .oo-ui-tool-link {
+ padding: 0.3125em 0 0.3125em 0.3125em;
+
+ .oo-ui-iconElement-icon {
+ height: 1.875em;
+ width: 1.875em;
+ min-width: 1.875em;
+ }
+
+ .oo-ui-tool-title {
+ padding-left: 0.5em;
+ }
+
+ .oo-ui-tool-accel,
+ .oo-ui-tool-title {
+ line-height: 2em;
+ }
+
+ .oo-ui-tool-accel {
+ color: #888;
+ }
+ }
+}
+
+.theme-oo-ui-listToolGroup () {
+ .oo-ui-toolGroup-tools {
+ padding: 0.3125em;
+ }
+
+ &.oo-ui-popupToolGroup-active {
+ border-color: rgba(0,0,0,0.2);
+ }
+
+ .oo-ui-tool {
+ border: 1px solid transparent;
+ margin: -1px 0;
+ padding: 0 0.625em 0 0;
+
+ &-active {
+ &.oo-ui-widget-enabled {
+ border-color: rgba(0,0,0,0.1);
+ box-shadow: inset 0 0.0875em 0.0875em 0 rgba(0, 0, 0, 0.07);
+ .oo-ui-vertical-gradient(#F1F7FB, #fff);
+
+ + .oo-ui-tool-active.oo-ui-widget-enabled {
+ border-top-color: rgba(0,0,0,0.1);
+ }
+ &:hover {
+ border-color: rgba(0,0,0,0.2);
+ }
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover {
+ border-color: rgba(0,0,0,0.2);
+ }
+ &:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
+ opacity: 1;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-tool-link {
+ .oo-ui-tool-title {
+ color: #ccc;
+ }
+
+ .oo-ui-tool-accel {
+ color: #ddd;
+ }
+
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ color: #ccc;
+ .oo-ui-indicatorElement-indicator,
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+}
+
+.theme-oo-ui-menuToolGroup () {
+ border-color: rgba(0,0,0,0.1);
+
+ .oo-ui-popupToolGroup-handle {
+ min-width: 10em;
+
+ .oo-ui-toolbar-narrow & {
+ min-width: 8.125em;
+ }
+ }
+
+ .oo-ui-toolGroup-tools {
+ padding: 0.3125em 0 0.3125em 0;
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover {
+ border-color: rgba(0,0,0,0.2);
+ }
+ }
+
+ &.oo-ui-popupToolGroup-active {
+ border-color: rgba(0,0,0,0.25);
+ }
+
+ .oo-ui-tool {
+ padding: 0 1.25em 0 0.3125em;
+
+ &-link {
+ .oo-ui-iconElement-icon {
+ background-image: none;
+ }
+ }
+
+ &-active .oo-ui-tool-link .oo-ui-iconElement-icon {
+ .oo-ui-background-image-svg('@{oo-ui-default-image-path}/icons/check');
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover {
+ background-color: #e1f3ff;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-tool-link .oo-ui-tool-title {
+ color: #ccc;
+ }
+
+ .oo-ui-tool-link .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ color: #ccc;
+ border-color: rgba(0,0,0,0.05);
+
+ .oo-ui-indicatorElement-indicator,
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/widgets.less b/vendor/oojs/oojs-ui/src/themes/apex/widgets.less
new file mode 100644
index 00000000..f415b6dd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/widgets.less
@@ -0,0 +1,797 @@
+@import 'common';
+
+.theme-oo-ui-widget () {}
+
+.theme-oo-ui-outlineControlsWidget () {
+ height: 3em;
+ background-color: #fff;
+
+ &-items,
+ &-movers {
+ height: 2em;
+ margin: 0.5em 0.5em 0.5em 0;
+ padding: 0;
+ }
+
+ > .oo-ui-iconElement-icon {
+ width: 1.5em;
+ height: 2em;
+ margin: 0.5em 0 0.5em 0.5em;
+ opacity: 0.2;
+ }
+}
+
+.theme-oo-ui-toggleWidget () {}
+
+.theme-oo-ui-buttonGroupWidget () {
+ display: inline-block;
+ white-space: nowrap;
+ border-radius: 0.3em;
+
+ .oo-ui-inline-spacing(0.5em);
+ .oo-ui-buttonElement {
+ .oo-ui-inline-spacing(0);
+ }
+
+ .oo-ui-buttonElement-framed {
+ .oo-ui-buttonElement-button {
+ border-radius: 0;
+ margin-left: -1px;
+ }
+
+ &:first-child .oo-ui-buttonElement-button {
+ border-bottom-left-radius: 0.3em;
+ border-top-left-radius: 0.3em;
+ margin-left: 0;
+ }
+
+ &:last-child .oo-ui-buttonElement-button {
+ border-bottom-right-radius: 0.3em;
+ border-top-right-radius: 0.3em;
+ }
+ }
+}
+
+.theme-oo-ui-buttonWidget () {
+ .oo-ui-inline-spacing(0.5em);
+}
+
+.theme-oo-ui-actionWidget () {
+ &.oo-ui-pendingElement-pending {
+ .oo-ui-background-image('@{oo-ui-default-image-path}/textures/pending.gif');
+ }
+}
+
+.theme-oo-ui-popupButtonWidget () {
+ &.oo-ui-buttonElement-frameless > .oo-ui-popupWidget {
+ // Compensate for icon being inset
+ /* @noflip */
+ left: 1em;
+ }
+
+ &.oo-ui-buttonElement-framed > .oo-ui-popupWidget {
+ // Compensate for icon being inset
+ /* @noflip */
+ left: 1.25em;
+ }
+}
+
+.theme-oo-ui-toggleButtonWidget () {
+ .oo-ui-inline-spacing(0.5em);
+}
+
+.theme-oo-ui-iconWidget () {
+ line-height: 2.5em;
+ height: @icon-size;
+ width: @icon-size;
+
+ &.oo-ui-widget-disabled {
+ opacity: 0.2;
+ }
+}
+
+.theme-oo-ui-indicatorWidget () {
+ line-height: 2.5em;
+ height: @indicator-size;
+ width: @indicator-size;
+ margin: @indicator-size / 2;
+
+ &.oo-ui-widget-disabled {
+ opacity: 0.2;
+ }
+}
+
+.theme-oo-ui-dropdownWidget () {
+ margin: 0.25em 0;
+ width: 100%;
+ max-width: 50em;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ &-handle {
+ height: 2.5em;
+ border: 1px solid rgba(0,0,0,0.1);
+ border-radius: 0.25em;
+
+ &:hover {
+ border-color: rgba(0,0,0,0.2);
+ }
+
+ .oo-ui-indicatorElement-indicator {
+ right: 0;
+ }
+
+ .oo-ui-iconElement-icon {
+ left: 0.25em;
+ }
+
+ .oo-ui-labelElement-label {
+ line-height: 2.5em;
+ margin: 0 0.5em;
+ }
+
+ .oo-ui-indicatorElement-indicator {
+ top: 0;
+ width: @indicator-size;
+ height: @indicator-size;
+ margin: 0.775em;
+ }
+
+ .oo-ui-iconElement-icon {
+ top: 0;
+ width: @icon-size;
+ height: @icon-size;
+ margin: 0.3em;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-dropdownWidget-handle {
+ color: #ccc;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #ddd;
+ background-color: #f3f3f3;
+ }
+ .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+ }
+ }
+
+ &.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+ margin-left: 3em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+ margin-right: 2em;
+ }
+}
+
+.theme-oo-ui-inputWidget () {
+ .oo-ui-inline-spacing(0.5em);
+}
+
+.theme-oo-ui-buttonInputWidget () {}
+
+.theme-oo-ui-checkboxInputWidget () {}
+
+.theme-oo-ui-dropdownInputWidget () {
+ width: 100%;
+ max-width: 50em;
+
+ select {
+ height: 2.5em;
+ padding: 0.5em;
+ font-size: inherit;
+ font-family: inherit;
+ border: 1px solid rgba(0,0,0,0.1);
+ border-radius: 0.25em;
+ }
+
+ &.oo-ui-widget-enabled {
+ select:hover,
+ select:focus {
+ border-color: rgba(0,0,0,0.2);
+ outline: none;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ select {
+ color: #ccc;
+ border-color: #ddd;
+ background-color: #f3f3f3;
+ }
+ }
+}
+
+.theme-oo-ui-radioInputWidget () {}
+
+.theme-oo-ui-textInputWidget () {
+ width: 100%;
+ max-width: 50em;
+
+ input,
+ textarea {
+ padding: 0.5em;
+ font-size: inherit;
+ font-family: inherit;
+ background-color: #fff;
+ color: black;
+ border: 1px solid #ccc;
+ box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #ddd;
+ border-radius: 0.25em;
+ .oo-ui-transition(border-color 200ms, box-shadow 200ms);
+ }
+
+ &-decorated {
+ input,
+ textarea {
+ padding-left: 2em;
+ }
+ }
+
+ &-icon {
+ width: 2em;
+ }
+
+ &.oo-ui-widget-enabled {
+ input:focus,
+ textarea:focus {
+ outline: none;
+ border-color: #a7dcff;
+ box-shadow: 0 0 0.3em #a7dcff, 0 0 0 white;
+ }
+
+ input[readonly],
+ textarea[readonly] {
+ color: #777;
+ }
+
+ &.oo-ui-flaggedElement-invalid {
+ input,
+ textarea {
+ background-color: #fdd;
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ input,
+ textarea {
+ color: #ccc;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #ddd;
+ background-color: #f3f3f3;
+ }
+ .oo-ui-iconElement-icon,
+ .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+ }
+ .oo-ui-labelElement-label {
+ color: #ddd;
+ text-shadow: 0 1px 1px #fff;
+ }
+ }
+
+ &.oo-ui-pendingElement-pending {
+ input,
+ textarea {
+ background-color: transparent;
+ .oo-ui-background-image('@{oo-ui-default-image-path}/textures/pending.gif');
+ }
+ }
+
+ &.oo-ui-iconElement {
+ input,
+ textarea {
+ padding-left: 2em;
+ }
+
+ .oo-ui-iconElement-icon {
+ width: @icon-size;
+ margin-left: 0.1em;
+ }
+ }
+
+ &.oo-ui-indicatorElement {
+ input,
+ textarea {
+ padding-right: 1.5em;
+ }
+
+ .oo-ui-indicatorElement-indicator {
+ width: @indicator-size;
+ margin-right: 0.775em;
+ }
+ }
+
+ > .oo-ui-labelElement-label {
+ padding: 0.4em;
+ line-height: 1.5em;
+ color: #888;
+ }
+
+ &-labelPosition-after {
+ &.oo-ui-indicatorElement > .oo-ui-labelElement-label {
+ margin-right: 1.6em;
+ }
+ }
+
+ &-labelPosition-before {
+ &.oo-ui-iconElement > .oo-ui-labelElement-label {
+ margin-left: 2.1em;
+ }
+ }
+}
+
+.theme-oo-ui-comboBoxWidget () {
+ width: 100%;
+ max-width: 50em;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ &-handle {
+ border: 1px solid rgba(0,0,0,0.1);
+ border-radius: 0.25em;
+
+ &:hover {
+ border-color: rgba(0,0,0,0.2);
+ }
+ }
+
+ &.oo-ui-widget-disabled,
+ &-empty {
+ .oo-ui-textInputWidget.oo-ui-indicatorElement {
+ .oo-ui-indicatorElement-indicator {
+ cursor: default;
+ opacity: 0.2;
+ }
+ }
+ }
+
+ > .oo-ui-selectWidget {
+ margin-top: -3px
+ }
+}
+
+.theme-oo-ui-labelWidget () {
+ padding: 0.5em 0;
+}
+
+.theme-oo-ui-optionWidget () {
+ padding: 0.25em 0.5em;
+ border: none;
+
+ &-highlighted {
+ background-color: #e1f3ff;
+ }
+
+ .oo-ui-labelElement-label {
+ line-height: 1.5em;
+ }
+
+ .oo-ui-selectWidget-depressed &-selected {
+ background-color: #a7dcff;
+ }
+
+ .oo-ui-selectWidget-pressed &-pressed,
+ .oo-ui-selectWidget-pressed &-pressed&-highlighted,
+ .oo-ui-selectWidget-pressed &-pressed&-highlighted&-selected {
+ background-color: #a7dcff;
+ }
+
+ &.oo-ui-widget-disabled {
+ color: #ccc;
+ }
+}
+
+.theme-oo-ui-decoratedOptionWidget () {
+ padding: 0.5em 2em 0.5em 3em;
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon,
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ top: 0;
+ height: 100%;
+ }
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon {
+ width: @icon-size;
+ left: 0.5em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ width: @indicator-size;
+ right: 0.5em;
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-iconElement-icon,
+ .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+ }
+ }
+}
+
+.theme-oo-ui-buttonOptionWidget () {
+ padding: 0;
+ background-color: transparent;
+
+ .oo-ui-buttonElement-button {
+ height: @icon-size;
+ }
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon {
+ margin-top: 0;
+ }
+
+ &.oo-ui-optionWidget-selected,
+ &.oo-ui-optionWidget-pressed,
+ &.oo-ui-optionWidget-highlighted {
+ background-color: transparent;
+ }
+}
+
+.theme-oo-ui-radioOptionWidget () {
+ padding: 0;
+ background-color: transparent;
+
+ &.oo-ui-optionWidget-selected,
+ &.oo-ui-optionWidget-pressed,
+ &.oo-ui-optionWidget-highlighted {
+ background-color: transparent;
+ }
+
+ &.oo-ui-labelElement .oo-ui-labelElement-label {
+ padding-left: 0.5em;
+ }
+
+ .oo-ui-radioInputWidget {
+ margin-right: 0;
+ }
+}
+
+.theme-oo-ui-menuOptionWidget () {
+ &.oo-ui-optionWidget {
+ &-selected {
+ background-color: transparent;
+ }
+ &-highlighted,
+ &-highlighted.oo-ui-optionWidget-selected {
+ background-color: #e1f3ff;
+ }
+ }
+}
+
+.theme-oo-ui-menuSectionOptionWidget () {
+ padding: 0.33em 0.75em;
+ color: #888;
+}
+
+.theme-oo-ui-outlineOptionWidget () {
+ font-size: 1.1em;
+ padding: 0.75em;
+
+ &.oo-ui-indicatorElement .oo-ui-labelElement-label {
+ padding-right: 1.5em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ opacity: 0.5;
+ }
+
+ &-level-0 {
+ padding-left: 3.5em;
+
+ .oo-ui-iconElement-icon {
+ left: 1em;
+ }
+ }
+ &-level-1 {
+ padding-left: 5em;
+
+ .oo-ui-iconElement-icon {
+ left: 2.5em;
+ }
+ }
+
+ &-level-2 {
+ padding-left: 6.5em;
+
+ .oo-ui-iconElement-icon {
+ left: 4em;
+ }
+ }
+
+ .oo-ui-selectWidget-depressed &.oo-ui-optionWidget-selected {
+ background-color: #a7dcff;
+ text-shadow: 0 1px 1px rgba(255,255,255,0.5);
+ }
+
+ &.oo-ui-flaggedElement-important {
+ font-weight: bold;
+ }
+
+ &.oo-ui-flaggedElement-placeholder {
+ font-style: italic;
+ }
+
+ &.oo-ui-flaggedElement-empty {
+ .oo-ui-iconElement-icon {
+ opacity: 0.5;
+ }
+ .oo-ui-labelElement-label {
+ color: #777;
+ }
+ }
+}
+
+.theme-oo-ui-tabOptionWidget () {
+ padding: 0.5em 1em;
+ margin: 0.5em 0 0 0.75em;
+ border: 1px solid transparent;
+ border-bottom: none;
+ border-top-left-radius: 0.5em;
+ border-top-right-radius: 0.5em;
+
+ &.oo-ui-indicatorElement .oo-ui-labelElement-label {
+ padding-right: 1.5em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ opacity: 0.5;
+ }
+
+ .oo-ui-selectWidget-pressed &.oo-ui-optionWidget-pressed {
+ background-color: transparent;
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.2);
+ border-color: #ddd;
+ }
+
+ &:active {
+ background-color: #fff;
+ border-color: #ddd;
+ }
+ }
+
+ .oo-ui-selectWidget-pressed &.oo-ui-optionWidget-selected,
+ .oo-ui-selectWidget-depressed &.oo-ui-optionWidget-selected,
+ &.oo-ui-optionWidget-selected:hover {
+ background-color: #fff;
+ border-color: #ddd;
+ }
+}
+
+.theme-oo-ui-popupWidget () {
+ &-popup {
+ border: 1px solid #ccc;
+ border-radius: 0.25em;
+ background-color: #fff;
+ box-shadow: 0 0.15em 0.5em 0 rgba(0, 0, 0, 0.2);
+ }
+
+ @anchor-size: 6px;
+
+ &-anchored {
+ .oo-ui-popupWidget-popup {
+ margin-top: @anchor-size;
+ }
+
+ .oo-ui-popupWidget-anchor:before,
+ .oo-ui-popupWidget-anchor:after {
+ content: "";
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-color: transparent;
+ border-top: 0;
+ }
+
+ .oo-ui-popupWidget-anchor:before {
+ bottom: -@anchor-size - 1px;
+ left: -@anchor-size;
+ border-bottom-color: #aaa;
+ border-width: @anchor-size + 1px;
+ }
+
+ .oo-ui-popupWidget-anchor:after {
+ bottom: -@anchor-size - 1px;
+ left: -@anchor-size + 1px;
+ border-bottom-color: #fff;
+ border-width: @anchor-size;
+ }
+ }
+
+ &-transitioning .oo-ui-popupWidget-popup {
+ .oo-ui-transition(
+ width 100ms ease-in-out, height 100ms ease-in-out, left 100ms ease-in-out
+ );
+ }
+
+ &-head {
+ height: 2.5em;
+
+ .oo-ui-buttonWidget {
+ margin: 0.25em;
+ }
+
+ .oo-ui-labelElement-label {
+ margin: 0.75em 1em;
+ }
+ }
+
+ &-body-padded {
+ padding: 0 1em;
+ }
+}
+
+.theme-oo-ui-searchWidget () {
+ &-query {
+ height: 4em;
+ padding: 0 1em;
+ box-shadow: 0 0 0.5em rgba(0,0,0,0.2);
+
+ .oo-ui-textInputWidget {
+ margin: 0.75em 0;
+ }
+ }
+
+ &-results {
+ top: 4em;
+ padding: 1em;
+ line-height: 0;
+ }
+}
+
+.theme-oo-ui-selectWidget () {}
+
+.theme-oo-ui-buttonSelectWidget () {
+ border-radius: 0.3em;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ .oo-ui-buttonOptionWidget {
+ .oo-ui-buttonElement-button {
+ border-radius: 0;
+ margin-left: -1px;
+ }
+
+ &:first-child .oo-ui-buttonElement-button {
+ border-bottom-left-radius: 0.3em;
+ border-top-left-radius: 0.3em;
+ margin-left: 0;
+ }
+
+ &:last-child .oo-ui-buttonElement-button {
+ border-bottom-right-radius: 0.3em;
+ border-top-right-radius: 0.3em;
+ }
+ }
+}
+
+.theme-oo-ui-radioSelectWidget () {
+ padding: 0.75em 0 0.5em 0;
+}
+
+.theme-oo-ui-menuSelectWidget () {
+ background: #fff;
+ margin-top: -1px;
+ border: 1px solid #ccc;
+ border-radius: 0 0 0.25em 0.25em;
+ box-shadow: 0 0.15em 1em 0 rgba(0, 0, 0, 0.2);
+}
+
+.theme-oo-ui-textInputMenuSelectWidget () {}
+
+.theme-oo-ui-outlineSelectWidget () {}
+
+.theme-oo-ui-tabSelectWidget () {
+ background-color: #eee;
+ box-shadow: inset 0 -0.015em 0.1em rgba(0, 0, 0, 0.1);
+}
+
+.theme-oo-ui-toggleSwitchWidget () {
+ @travelDistance: 2em;
+ height: 2em;
+ width: @travelDistance + 2em;
+ border-radius: 1em;
+ box-shadow: 0 0 0 white, inset 0 0.1em 0.2em #ddd;
+ border: 1px solid #ccc;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ .oo-ui-vertical-gradient(#ddd, #fff);
+
+ &.oo-ui-widget-disabled {
+ opacity: 0.5;
+ }
+
+ &-grip {
+ top: 0.25em;
+ left: 0.25em;
+ width: 1.5em;
+ height: 1.5em;
+ margin-top: -1px;
+ border-radius: 1em;
+ box-shadow: 0 0.1em 0.25em rgba(0, 0, 0, 0.1);
+ border: 1px #c9c9c9 solid;
+
+ .oo-ui-transition(left 200ms ease-in-out, margin-left 200ms ease-in-out);
+ .oo-ui-vertical-gradient(#fff, #ddd);
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover,
+ &:hover .oo-ui-toggleSwitchWidget-grip {
+ border-color: #aaa;
+ }
+ }
+
+ .oo-ui-toggleSwitchWidget-glow {
+ border-radius: 1em;
+ box-shadow: inset 0 1px 4px 0 rgba(0, 0, 0, 0.07);
+
+ .oo-ui-transition(opacity 200ms ease-in-out);
+ .oo-ui-vertical-gradient(#b0d9ee, #eaf4fa);
+ }
+
+ .oo-ui-toggleWidget-on & {
+ &-glow {
+ opacity: 1;
+ }
+ &-grip {
+ left: @travelDistance + 0.25em;
+ margin-left: -2px;
+ }
+ }
+
+ .oo-ui-toggleWidget-off & {
+ &-glow {
+ display: block;
+ opacity: 0;
+ }
+ &-grip {
+ left: 0.25em;
+ margin-left: 0;
+ }
+ }
+}
+
+.theme-oo-ui-progressBarWidget () {
+ max-width: 50em;
+ border: 1px solid #ccc;
+ border-radius: 0.25em;
+ overflow: hidden;
+
+ &-bar {
+ height: 1em;
+ border-right: 1px solid #ccc;
+ .oo-ui-transition(width 200ms, margin-left 200ms);
+ .oo-ui-vertical-gradient(@progressive-gradient-start, @progressive-gradient-end);
+ }
+ &-indeterminate {
+ .oo-ui-progressBarWidget-bar {
+ .oo-ui-animation(oo-ui-progressBarWidget-slide 2s infinite linear);
+ width: 40%;
+ margin-left: -10%;
+ border-left: 1px solid @progressive-border;
+ }
+ }
+ &.oo-ui-widget-disabled {
+ opacity: 0.6;
+ }
+}
+
+.oo-ui-progressBarWidget-slide-frames () {
+ from { margin-left: -40%; }
+ to { margin-left: 100%; }
+}
+@-webkit-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@-moz-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@-ms-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@-o-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
diff --git a/vendor/oojs/oojs-ui/src/themes/apex/windows.less b/vendor/oojs/oojs-ui/src/themes/apex/windows.less
new file mode 100644
index 00000000..0048c7ff
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/apex/windows.less
@@ -0,0 +1,307 @@
+@import 'common';
+
+.theme-oo-ui-window () {
+ background-color: transparent;
+ background-image: none;
+}
+
+.theme-oo-ui-dialog () {
+ &-content > .oo-ui-window-body {
+ box-shadow: 0 0 0.66em rgba(0,0,0,0.25);
+ }
+}
+
+.theme-oo-ui-messageDialog () {
+ &-content {
+ .oo-ui-window-body {
+ box-shadow: 0 0 0.33em rgba(0,0,0,0.33);
+ }
+ }
+
+ &-title,
+ &-message {
+ display: block;
+ text-align: center;
+ padding-top: 0.5em;
+ }
+
+ &-title {
+ font-size: 1.5em;
+ line-height: 1em;
+ color: #000;
+ }
+
+ &-message {
+ font-size: 0.9em;
+ line-height: 1.25em;
+ color: #666;
+
+ &-verbose {
+ font-size: 1.1em;
+ line-height: 1.5em;
+ text-align: left;
+ }
+ }
+
+ &-actions {
+ &-horizontal {
+ .oo-ui-actionWidget {
+ border-right: 1px solid #e5e5e5;
+
+ &:last-child {
+ border-right-width: 0;
+ }
+ }
+ }
+
+ &-vertical {
+ .oo-ui-actionWidget {
+ border-bottom: 1px solid #e5e5e5;
+
+ &:last-child {
+ border-bottom-width: 0;
+ }
+ }
+ }
+
+ .oo-ui-actionWidget {
+ height: 3.4em;
+
+ &.oo-ui-labelElement .oo-ui-labelElement-label {
+ text-align: center;
+ line-height: 3.4em;
+ padding: 0 2em;
+ }
+
+ &:hover {
+ background-color: rgba(0,0,0,0.05);
+ }
+
+ &:active {
+ background-color: rgba(0,0,0,0.1);
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive {
+ &:hover {
+ background-color: rgba(8,126,204,0.05);
+ }
+
+ &:active {
+ background-color: rgba(8,126,204,0.1);
+ }
+
+ .oo-ui-labelElement-label {
+ font-weight: bold;
+ }
+ }
+
+ &-constructive {
+ &:hover {
+ background-color: rgba(118,171,54,0.05);
+ }
+
+ &:active {
+ background-color: rgba(118,171,54,0.1);
+ }
+ }
+
+ &-destructive {
+ &:hover {
+ background-color: rgba(212,83,83,0.05);
+ }
+
+ &:active {
+ background-color: rgba(212,83,83,0.1);
+ }
+ }
+ }
+ }
+ }
+}
+
+.theme-oo-ui-processDialog () {
+ &-content {
+ .oo-ui-window-head {
+ height: 3.4em;
+
+ &.oo-ui-pendingElement-pending {
+ .oo-ui-background-image('@{oo-ui-default-image-path}/textures/pending.gif');
+ }
+ }
+
+ .oo-ui-window-body {
+ top: 3.4em;
+ box-shadow: 0 0 0.33em rgba(0,0,0,0.33);
+ }
+ }
+
+ &-navigation {
+ position: relative;
+ height: 3.4em;
+ padding: 0 1em;
+ }
+
+ &-location {
+ padding: 0.75em 0;
+ height: @icon-size;
+ cursor: default;
+ text-align: center;
+ }
+
+ &-title {
+ font-weight: bold;
+ line-height: @icon-size;
+ }
+
+ &-actions {
+ &-safe,
+ &-primary,
+ &-other {
+ .oo-ui-actionWidget {
+ .oo-ui-buttonElement-button {
+ min-width: @icon-size;
+ min-height: @icon-size;
+ }
+
+ .oo-ui-labelElement-label {
+ line-height: @icon-size;
+ }
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon {
+ margin-top: -0.125em;
+ }
+
+ &.oo-ui-buttonElement-framed {
+ margin: 0.75em 0 0.75em 0.75em;
+ .oo-ui-buttonElement-button {
+ padding: 0 1em;
+ vertical-align: middle;
+ }
+ }
+ }
+ }
+
+ &-safe,
+ &-primary {
+ .oo-ui-actionWidget {
+ &:hover {
+ background-color: rgba(0,0,0,0.05);
+ }
+
+ &:active {
+ background-color: rgba(0,0,0,0.1);
+ }
+
+ &.oo-ui-buttonElement-framed {
+ margin: 0.75em;
+ .oo-ui-buttonElement-button {
+ /* Adjust for border so text aligns with title */
+ margin: -1px;
+ }
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive {
+ &:hover {
+ background-color: rgba(8,126,204,0.05);
+ }
+
+ &:active {
+ background-color: rgba(8,126,204,0.1);
+ }
+
+ .oo-ui-labelElement-label {
+ font-weight: bold;
+ }
+ }
+
+ &-constructive {
+ &:hover {
+ background-color: rgba(118,171,54,0.05);
+ }
+
+ &:active {
+ background-color: rgba(118,171,54,0.1);
+ }
+ }
+
+ &-destructive {
+ &:hover {
+ background-color: rgba(212,83,83,0.05);
+ }
+
+ &:active {
+ background-color: rgba(212,83,83,0.1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ > .oo-ui-window-frame {
+ min-height: 5em;
+ }
+
+ &-errors {
+ background-color: rgba(255,255,255,0.9);
+ padding: 3em 3em 1.5em 3em;
+ text-align: center;
+
+ .oo-ui-buttonWidget {
+ margin: 2em 1em 2em 1em;
+ }
+
+ &-title {
+ font-size: 1.5em;
+ color: #000;
+ margin-bottom: 2em;
+ }
+ }
+
+ &-error {
+ text-align: left;
+ margin: 1em;
+ padding: 1em;
+ border: 1px solid #ff9e9e;
+ background-color: #fff7f7;
+ border-radius: 0.25em;
+ }
+}
+
+.theme-oo-ui-windowManager () {
+ &-modal > .oo-ui-dialog {
+ background-color: rgba(255,255,255,0.5);
+ opacity: 0;
+
+ .oo-ui-transition(opacity 250ms ease-in-out);
+
+ > .oo-ui-window-frame {
+ top: 1em;
+ bottom: 1em;
+ background-color: #fff;
+
+ opacity: 0;
+ .oo-ui-transform(scale(0.5));
+ .oo-ui-transition(all 250ms ease-in-out);
+ }
+
+ &.oo-ui-window-ready {
+ /* Fade window overlay */
+ opacity: 1;
+
+ > .oo-ui-window-frame {
+ /* Fade frame */
+ opacity: 1;
+ .oo-ui-transform(scale(1));
+ }
+ }
+ }
+
+ &-modal&-floating > .oo-ui-dialog > .oo-ui-window-frame {
+ border: 1px solid #ccc;
+ border-radius: 0.5em;
+ box-shadow: 0 0.2em 1em rgba(0, 0, 0, 0.3);
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/BlankTheme.js b/vendor/oojs/oojs-ui/src/themes/blank/BlankTheme.js
new file mode 100644
index 00000000..208b6a9c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/BlankTheme.js
@@ -0,0 +1,32 @@
+/**
+ * @class
+ * @extends {OO.ui.Theme}
+ *
+ * @constructor
+ */
+OO.ui.BlankTheme = function OoUiBlankTheme() {
+ // Parent constructor
+ OO.ui.BlankTheme.super.call( this );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.BlankTheme, OO.ui.Theme );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.BlankTheme.prototype.getElementClasses = function ( element ) {
+ // Parent method
+ var classes = OO.ui.BlankTheme.super.prototype.getElementClasses.call( this, element );
+
+ // Add classes to classes.on or classes.off
+
+ return classes;
+};
+
+/* Instantiation */
+
+OO.ui.theme = new OO.ui.BlankTheme();
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/common.less b/vendor/oojs/oojs-ui/src/themes/blank/common.less
new file mode 100644
index 00000000..f9ec869a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/common.less
@@ -0,0 +1,10 @@
+// Base variables and mixins
+@import '../../styles/common';
+
+// Theme variables
+
+@oo-ui-default-image-path: 'themes/blank/images';
+
+// Theme mixins
+
+// (add mixins here)
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/core.less b/vendor/oojs/oojs-ui/src/themes/blank/core.less
new file mode 100644
index 00000000..1c83a946
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/core.less
@@ -0,0 +1,12 @@
+// Base and theme variables and mixins
+@import 'common';
+
+// Theme rules
+@import 'elements';
+@import 'layouts';
+@import 'tools';
+@import 'widgets';
+@import 'windows';
+
+// Base rules
+@import '../../styles/core';
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/elements.less b/vendor/oojs/oojs-ui/src/themes/blank/elements.less
new file mode 100644
index 00000000..89b96648
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/elements.less
@@ -0,0 +1,29 @@
+@import 'common';
+
+.theme-oo-ui-element () {}
+
+.theme-oo-ui-buttonElement () {}
+
+.theme-oo-ui-clippableElement () {}
+
+.theme-oo-ui-draggableElement () {}
+
+.theme-oo-ui-flaggedElement () {}
+
+.theme-oo-ui-groupElement () {}
+
+.theme-oo-ui-draggableGroupElement () {}
+
+.theme-oo-ui-iconElement () {}
+
+.theme-oo-ui-indicatorElement () {}
+
+.theme-oo-ui-labelElement () {}
+
+.theme-oo-ui-lookupElement () {}
+
+.theme-oo-ui-popupElement () {}
+
+.theme-oo-ui-tabIndexedElement () {}
+
+.theme-oo-ui-titledElement () {}
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/images/icons/README.txt b/vendor/oojs/oojs-ui/src/themes/blank/images/icons/README.txt
new file mode 100644
index 00000000..129fbed2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/images/icons/README.txt
@@ -0,0 +1 @@
+Remove this file and add icon images here \ No newline at end of file
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/images/indicators/README.txt b/vendor/oojs/oojs-ui/src/themes/blank/images/indicators/README.txt
new file mode 100644
index 00000000..9ff30ec2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/images/indicators/README.txt
@@ -0,0 +1 @@
+Remove this file and add indicator images here \ No newline at end of file
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/images/textures/README.txt b/vendor/oojs/oojs-ui/src/themes/blank/images/textures/README.txt
new file mode 100644
index 00000000..6ca283de
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/images/textures/README.txt
@@ -0,0 +1 @@
+Remove this file and add texture images here \ No newline at end of file
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/layouts.less b/vendor/oojs/oojs-ui/src/themes/blank/layouts.less
new file mode 100644
index 00000000..93f677c7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/layouts.less
@@ -0,0 +1,26 @@
+@import 'common';
+
+.theme-oo-ui-layout () {}
+
+.theme-oo-ui-bookletLayout () {}
+
+.theme-oo-ui-indexLayout () {}
+
+.theme-oo-ui-fieldLayout () {}
+
+.theme-oo-ui-actionFieldLayout () {}
+
+.theme-oo-ui-fieldsetLayout () {}
+
+.theme-oo-ui-formLayout () {}
+
+.theme-oo-ui-menuLayout () {}
+
+.theme-oo-ui-panelLayout () {}
+
+.theme-oo-ui-cardLayout () {}
+
+.theme-oo-ui-pageLayout () {}
+
+.theme-oo-ui-stackLayout () {}
+
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/tools.less b/vendor/oojs/oojs-ui/src/themes/blank/tools.less
new file mode 100644
index 00000000..923b0ad6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/tools.less
@@ -0,0 +1,20 @@
+@import 'common';
+
+.theme-oo-ui-toolbar () {}
+
+.theme-oo-ui-tool () {}
+
+.theme-oo-ui-popupTool () {}
+
+.theme-oo-ui-toolGroupTool () {}
+
+.theme-oo-ui-toolGroup () {}
+
+.theme-oo-ui-barToolGroup () {}
+
+.theme-oo-ui-popupToolGroup () {}
+
+.theme-oo-ui-listToolGroup () {}
+
+.theme-oo-ui-menuToolGroup () {}
+
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/widgets.less b/vendor/oojs/oojs-ui/src/themes/blank/widgets.less
new file mode 100644
index 00000000..68c64cd8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/widgets.less
@@ -0,0 +1,77 @@
+@import 'common';
+
+.theme-oo-ui-widget () {}
+
+.theme-oo-ui-outlineControlsWidget () {}
+
+.theme-oo-ui-toggleWidget () {}
+
+.theme-oo-ui-buttonGroupWidget () {}
+
+.theme-oo-ui-buttonWidget () {}
+
+.theme-oo-ui-actionWidget () {}
+
+.theme-oo-ui-popupButtonWidget () {}
+
+.theme-oo-ui-toggleButtonWidget () {}
+
+.theme-oo-ui-iconWidget () {}
+
+.theme-oo-ui-indicatorWidget () {}
+
+.theme-oo-ui-dropdownWidget () {}
+
+.theme-oo-ui-inputWidget () {}
+
+.theme-oo-ui-buttonInputWidget () {}
+
+.theme-oo-ui-checkboxInputWidget () {}
+
+.theme-oo-ui-dropdownInputWidget () {}
+
+.theme-oo-ui-radioInputWidget () {}
+
+.theme-oo-ui-textInputWidget () {}
+
+.theme-oo-ui-comboBoxWidget () {}
+
+.theme-oo-ui-labelWidget () {}
+
+.theme-oo-ui-optionWidget () {}
+
+.theme-oo-ui-decoratedOptionWidget () {}
+
+.theme-oo-ui-buttonOptionWidget () {}
+
+.theme-oo-ui-radioOptionWidget () {}
+
+.theme-oo-ui-menuOptionWidget () {}
+
+.theme-oo-ui-menuSectionOptionWidget () {}
+
+.theme-oo-ui-outlineOptionWidget () {}
+
+.theme-oo-ui-tabOptionWidget () {}
+
+.theme-oo-ui-popupWidget () {}
+
+.theme-oo-ui-searchWidget () {}
+
+.theme-oo-ui-selectWidget () {}
+
+.theme-oo-ui-buttonSelectWidget () {}
+
+.theme-oo-ui-radioSelectWidget () {}
+
+.theme-oo-ui-menuSelectWidget () {}
+
+.theme-oo-ui-textInputMenuSelectWidget () {}
+
+.theme-oo-ui-outlineSelectWidget () {}
+
+.theme-oo-ui-tabSelectWidget () {}
+
+.theme-oo-ui-toggleSwitchWidget () {}
+
+.theme-oo-ui-progressBarWidget () {}
diff --git a/vendor/oojs/oojs-ui/src/themes/blank/windows.less b/vendor/oojs/oojs-ui/src/themes/blank/windows.less
new file mode 100644
index 00000000..35c0b40a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/blank/windows.less
@@ -0,0 +1,11 @@
+@import 'common';
+
+.theme-oo-ui-window () {}
+
+.theme-oo-ui-dialog () {}
+
+.theme-oo-ui-messageDialog () {}
+
+.theme-oo-ui-processDialog () {}
+
+.theme-oo-ui-windowManager () {}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/MediaWikiTheme.js b/vendor/oojs/oojs-ui/src/themes/mediawiki/MediaWikiTheme.js
new file mode 100644
index 00000000..be2371bd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/MediaWikiTheme.js
@@ -0,0 +1,56 @@
+/**
+ * @class
+ * @extends OO.ui.Theme
+ *
+ * @constructor
+ */
+OO.ui.MediaWikiTheme = function OoUiMediaWikiTheme() {
+ // Parent constructor
+ OO.ui.MediaWikiTheme.super.call( this );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MediaWikiTheme, OO.ui.Theme );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MediaWikiTheme.prototype.getElementClasses = function ( element ) {
+ // Parent method
+ var variant,
+ variants = {
+ warning: false,
+ invert: false,
+ progressive: false,
+ constructive: false,
+ destructive: false
+ },
+ // Parent method
+ classes = OO.ui.MediaWikiTheme.super.prototype.getElementClasses.call( this, element ),
+ isFramed;
+
+ if ( element.supports( [ 'hasFlag' ] ) ) {
+ isFramed = element.supports( [ 'isFramed' ] ) && element.isFramed();
+ if ( isFramed && ( element.isDisabled() || element.hasFlag( 'primary' ) ) ) {
+ variants.invert = true;
+ } else {
+ variants.progressive = element.hasFlag( 'progressive' );
+ variants.constructive = element.hasFlag( 'constructive' );
+ variants.destructive = element.hasFlag( 'destructive' );
+ variants.warning = element.hasFlag( 'warning' );
+ }
+ }
+
+ for ( variant in variants ) {
+ classes[ variants[ variant ] ? 'on' : 'off' ].push( 'oo-ui-image-' + variant );
+ }
+
+ return classes;
+};
+
+/* Instantiation */
+
+OO.ui.theme = new OO.ui.MediaWikiTheme();
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/common.less b/vendor/oojs/oojs-ui/src/themes/mediawiki/common.less
new file mode 100644
index 00000000..ab63c022
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/common.less
@@ -0,0 +1,69 @@
+// Base variables and mixins
+@import '../../styles/common';
+
+// Theme variables
+
+@active: #999;
+@background: #ffffff;
+
+@select: #d8e6fe;
+
+@progressive: #347bff;
+@progressive-hover: #2962CC;
+@progressive-selected: #1F4999;
+@progressive-fade: rgba(52,123,255,0.1);
+
+@constructive: #00af89;
+@constructive-hover: #008064;
+@constructive-selected: #005946;
+@constructive-fade: rgba(0,171,137,0.1);
+
+@destructive: #d11d13;
+@destructive-hover: #8C130D;
+@destructive-selected: #73100A;
+@destructive-fade: rgba(209,29,19,0.1);
+
+@text: #555555;
+@pressed-text: #444444;
+@pressed-color: #d0d0d0; // Used for borders and backgrounds
+@disabled-text: #cccccc;
+@disabled-framed-text: #ffffff;
+@disabled-background: #dddddd;
+
+@neutral-button-border: 1px solid #cdcdcd;
+
+@oo-ui-default-image-path: 'themes/mediawiki/images';
+
+@input-border-color: #777;
+@input-active-color: #ddd;
+@input-disabled-color: #eee;
+@input-hover-border-bottom-width: 3px;
+@input-focus-border-width: 2px;
+@input-size: 1.6em;
+@border-radius: 2px;
+
+@icon-size: unit(24 / 16 / 0.8, em);
+@indicator-size: unit(12 / 16 / 0.8, em);
+
+// Theme animation variables
+@quick-ease: 0.1s ease-in-out;
+@medium-ease-out-back: 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+@medium-ease-out-sine: 0.2s cubic-bezier(0.39, 0.575, 0.565, 1);
+
+// Theme mixins
+
+// Workaround for Safari 8 bug. Combining a selector like `input[type="checkbox"]:checked + span`
+// with transition on background-size, background-color, and a single background-image using SVG
+// causes the selector to sometimes not be applied. (T89309)
+//
+// * Syntax mimics the core mixin .oo-ui-background-image-svg().
+// * No-op in distributions other than 'vector'.
+// * Using -webkit- prefix to limit this stupidity from impacting other browsers. Alas, some
+// non-Safari ones also parse the -webkit- prefix (Chrome, Opera).
+// * We take the payload size hit of the unnecessary /* @embed */ hint across the board. It should
+// be mostly mitigated by using gzip compression.
+// * Upstream bug report: https://bugs.webkit.org/show_bug.cgi?id=141789
+.oo-ui-background-image-safari( @url-without-extension ) when ( @oo-ui-distribution = vector ) {
+ @svg: '@{url-without-extension}.svg';
+ background-image: -webkit-linear-gradient(transparent, transparent), e('/* @embed */') url(@svg);
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/core.less b/vendor/oojs/oojs-ui/src/themes/mediawiki/core.less
new file mode 100644
index 00000000..1c83a946
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/core.less
@@ -0,0 +1,12 @@
+// Base and theme variables and mixins
+@import 'common';
+
+// Theme rules
+@import 'elements';
+@import 'layouts';
+@import 'tools';
+@import 'widgets';
+@import 'windows';
+
+// Base rules
+@import '../../styles/core';
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/elements.less b/vendor/oojs/oojs-ui/src/themes/mediawiki/elements.less
new file mode 100644
index 00000000..535c0251
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/elements.less
@@ -0,0 +1,319 @@
+@import 'common';
+
+.theme-oo-ui-element () {}
+
+.theme-oo-ui-buttonElement () {
+ > .oo-ui-buttonElement-button {
+ font-weight: bold;
+ }
+
+ &.oo-ui-iconElement > .oo-ui-buttonElement-button {
+ > .oo-ui-iconElement-icon {
+ margin-left: 0;
+ }
+ }
+
+ &.oo-ui-indicatorElement > .oo-ui-buttonElement-button {
+ > .oo-ui-indicatorElement-indicator {
+ width: @indicator-size;
+ height: @indicator-size;
+ margin: @indicator-size / 2;
+ }
+ }
+
+ &.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ margin-left: @indicator-size / 2;
+ }
+ &.oo-ui-iconElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ width: @icon-size;
+ height: @icon-size;
+ }
+
+ &-frameless {
+ > .oo-ui-buttonElement-button {
+ &:focus {
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,0.2), 0 0 0 1px rgba(0,0,0,0.2);
+ outline: none;
+ }
+
+ .oo-ui-indicatorElement-indicator {
+ margin-right: 0em;
+ }
+ }
+
+ &.oo-ui-labelElement {
+ > .oo-ui-buttonElement-button {
+ > .oo-ui-labelElement-label {
+ margin-left: 0.25em;
+ margin-right: 0.25em;
+ }
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ color: @text;
+ }
+
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ color: @pressed-text;
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive {
+ .mediawiki-frameless-button-colored(@progressive, @progressive-selected);
+ }
+
+ &-constructive {
+ .mediawiki-frameless-button-colored(@constructive, @constructive-selected);
+ }
+
+ &-destructive {
+ .mediawiki-frameless-button-colored(@destructive, @destructive-selected);
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
+ color: @disabled-text;
+
+ > .oo-ui-iconElement-icon,
+ > .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+ }
+ }
+ }
+
+ &-framed {
+ > .oo-ui-buttonElement-button {
+ margin: 0.1em 0;
+ padding: 0.2em 0.8em;
+ border-radius: @border-radius;
+
+ &:hover,
+ &:focus {
+ outline: none;
+ }
+
+ .oo-ui-transition(
+ background @quick-ease,
+ color @quick-ease,
+ box-shadow @quick-ease
+ );
+ }
+
+ // Support <input/> from ButtonInputWidget
+ > input.oo-ui-buttonElement-button,
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ line-height: @icon-size;
+ }
+
+ &.oo-ui-iconElement {
+ > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ margin-left: -0.5em;
+ margin-right: -0.5em;
+ }
+
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-iconElement-icon {
+ margin-right: 0.3em;
+ }
+ }
+
+ &.oo-ui-indicatorElement {
+ > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ /* -0.5 - 0.475 */
+ margin-left: -0.005em;
+ margin-right: -0.005em;
+ }
+
+ &.oo-ui-labelElement > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator,
+ &.oo-ui-iconElement:not( .oo-ui-labelElement ) > .oo-ui-buttonElement-button > .oo-ui-indicatorElement-indicator {
+ margin-left: @indicator-size / 2;
+ margin-right: -0.275em;
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-buttonElement-button {
+ color: @disabled-framed-text;
+ background: @disabled-background;
+ border: 1px solid @disabled-background;
+ }
+
+ &.oo-ui-widget-enabled {
+ > .oo-ui-buttonElement-button {
+ color: @text;
+ background-color: @background;
+ border: @neutral-button-border;
+
+ &:hover {
+ background-color: darken(@background,8%);
+ }
+
+ &:focus {
+ box-shadow: inset 0 0 0 1px rgba(0,0,0,0.2);
+ }
+ }
+
+ & > .oo-ui-buttonElement-button:active,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ background-color: darken(@background,15%);
+ border-color: darken(@background,15%);
+ box-shadow: none;
+ }
+
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
+ background-color: @active;
+ color: #fff;
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive {
+ .mediawiki-framed-button-colored(@progressive, @progressive-fade, @progressive-selected);
+ }
+
+ &-constructive {
+ .mediawiki-framed-button-colored(@constructive, @constructive-fade, @constructive-selected);
+ }
+
+ &-destructive {
+ .mediawiki-framed-button-colored(@destructive, @destructive-fade, @destructive-selected);
+ }
+ }
+ &.oo-ui-flaggedElement-primary.oo-ui-flaggedElement {
+ &-progressive {
+ .mediawiki-framed-primary-button-colored(@progressive, @progressive-hover, @progressive-selected);
+ }
+
+ &-constructive {
+ .mediawiki-framed-primary-button-colored(@constructive, @constructive-hover, @constructive-selected);
+ }
+
+ &-destructive {
+ .mediawiki-framed-primary-button-colored(@destructive, @destructive-hover, @destructive-selected);
+ }
+ }
+ }
+ }
+}
+
+.mediawiki-frameless-button-colored( @neutral, @pressed ) {
+ > .oo-ui-buttonElement-button {
+ &:hover,
+ &:focus {
+ > .oo-ui-labelElement-label {
+ color: @neutral;
+ }
+ }
+
+ > .oo-ui-labelElement-label {
+ color: #777777;
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ & > .oo-ui-buttonElement-button:active > .oo-ui-labelElement-label,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button > .oo-ui-labelElement-label {
+ color: @pressed;
+ box-shadow: none;
+ }
+ }
+}
+
+.mediawiki-framed-button-colored( @neutral, @hover, @pressed ) {
+ > .oo-ui-buttonElement-button {
+ color: @neutral;
+
+ &:hover {
+ background-color: @hover;
+ border-color: fade(@pressed,50%);
+ }
+
+ &:focus {
+ box-shadow: inset 0 0 0 1px @pressed;
+ border-color: @pressed;
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ .oo-ui-buttonElement-button:active,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ color: @pressed;
+ border-color: @pressed;
+ box-shadow: none;
+ }
+
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
+ background-color: @active;
+ color: #fff;
+ }
+ }
+}
+
+.mediawiki-framed-primary-button-colored( @neutral, @hover, @pressed ) {
+ > .oo-ui-buttonElement-button {
+ color: @background;
+ background-color: @neutral;
+ border-color: @neutral;
+
+ &:hover {
+ background: @hover;
+ border-color: @hover;
+ }
+
+ &:focus {
+ box-shadow: inset 0 0 0 1px @background;
+ border-color: @neutral;
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ .oo-ui-buttonElement-button:active,
+ &.oo-ui-buttonElement-pressed > .oo-ui-buttonElement-button {
+ color: @background;
+ background-color: @pressed;
+ border-color: @pressed;
+ box-shadow: none;
+ }
+
+ &.oo-ui-buttonElement-active > .oo-ui-buttonElement-button {
+ background-color: @active;
+ color: #fff;
+ }
+ }
+}
+
+.theme-oo-ui-clippableElement () {}
+
+.theme-oo-ui-flaggedElement () {}
+
+.theme-oo-ui-draggableElement () {}
+
+.theme-oo-ui-groupElement () {}
+
+.theme-oo-ui-draggableGroupElement () {}
+
+.theme-oo-ui-iconElement () {
+ .oo-ui-iconElement-icon,
+ &.oo-ui-iconElement-icon {
+ background-size: contain;
+ background-position: center center;
+ }
+}
+
+.theme-oo-ui-indicatorElement () {
+ .oo-ui-indicatorElement-indicator,
+ &.oo-ui-indicatorElement-indicator {
+ background-size: contain;
+ background-position: center center;
+ }
+}
+
+.theme-oo-ui-labelElement () {}
+
+.theme-oo-ui-lookupElement () {}
+
+.theme-oo-ui-popupElement () {}
+
+.theme-oo-ui-tabIndexedElement () {}
+
+.theme-oo-ui-titledElement () {}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-alerts.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-alerts.json
new file mode 100644
index 00000000..5fbf34d0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-alerts.json
@@ -0,0 +1,33 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "bell": { "file": "images/icons/bell.svg" },
+ "bellOn": { "file": {
+ "ltr": "images/icons/bellOn-ltr.svg",
+ "rtl": "images/icons/bellOn-rtl.svg"
+ } },
+ "eye": { "file": "images/icons/eye.svg" },
+ "eyeClosed": { "file": "images/icons/eyeClosed.svg" },
+ "message": { "file": {
+ "ltr": "images/icons/message-ltr.svg",
+ "rtl": "images/icons/message-rtl.svg"
+ } },
+ "signature": { "file": {
+ "ltr": "images/icons/signature-ltr.svg",
+ "rtl": "images/icons/signature-rtl.svg"
+ } },
+ "speechBubble": { "file": {
+ "ltr": "images/icons/speechBubble-ltr.svg",
+ "rtl": "images/icons/speechBubble-rtl.svg"
+ } },
+ "speechBubbleAdd": { "file": {
+ "ltr": "images/icons/speechBubbleAdd-ltr.svg",
+ "rtl": "images/icons/speechBubbleAdd-rtl.svg"
+ } },
+ "speechBubbles": { "file": {
+ "ltr": "images/icons/speechBubbles-ltr.svg",
+ "rtl": "images/icons/speechBubbles-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-content.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-content.json
new file mode 100644
index 00000000..29681c0e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-content.json
@@ -0,0 +1,50 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "article": { "file": {
+ "ltr": "images/icons/article-ltr.svg",
+ "rtl": "images/icons/article-rtl.svg"
+ } },
+ "articleCheck": { "file": {
+ "ltr": "images/icons/articleCheck-ltr.svg",
+ "rtl": "images/icons/articleCheck-rtl.svg"
+ } },
+ "articleSearch": { "file": {
+ "ltr": "images/icons/articleSearch-ltr.svg",
+ "rtl": "images/icons/articleSearch-rtl.svg"
+ } },
+ "book": { "file": {
+ "ltr": "images/icons/book-ltr.svg",
+ "rtl": "images/icons/book-rtl.svg"
+ } },
+ "citeArticle": { "file": {
+ "ltr": "images/icons/citeArticle-ltr.svg",
+ "rtl": "images/icons/citeArticle-rtl.svg"
+ } },
+ "die": { "file": {
+ "ltr": "images/icons/die-ltr.svg",
+ "rtl": "images/icons/die-rtl.svg"
+ } },
+ "download": { "file": {
+ "ltr": "images/icons/download-ltr.svg",
+ "rtl": "images/icons/download-rtl.svg"
+ } },
+ "folderPlaceholder": { "file": {
+ "ltr": "images/icons/folderPlaceholder-ltr.svg",
+ "rtl": "images/icons/folderPlaceholder-rtl.svg"
+ } },
+ "journal": { "file": {
+ "ltr": "images/icons/journal-ltr.svg",
+ "rtl": "images/icons/journal-rtl.svg"
+ } },
+ "newspaper": { "file": {
+ "ltr": "images/icons/newspaper-ltr.svg",
+ "rtl": "images/icons/newspaper-rtl.svg"
+ } },
+ "upload": { "file": {
+ "ltr": "images/icons/upload-ltr.svg",
+ "rtl": "images/icons/upload-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-advanced.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-advanced.json
new file mode 100644
index 00000000..8fdc5051
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-advanced.json
@@ -0,0 +1,75 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "alignCentre": { "file": "images/icons/align-center.svg" },
+ "alignLeft": { "file": "images/icons/align-float-left.svg" },
+ "alignRight": { "file": "images/icons/align-float-right.svg" },
+ "find": { "file": {
+ "ltr": "images/icons/find-ltr.svg",
+ "rtl": "images/icons/find-rtl.svg"
+ } },
+ "insert": { "file": "images/icons/insert.svg" },
+ "layout": { "file": {
+ "ltr": "images/icons/layout-ltr.svg",
+ "rtl": "images/icons/layout-rtl.svg"
+ } },
+ "newline": { "file": {
+ "ltr": "images/icons/newline-ltr.svg",
+ "rtl": "images/icons/newline-rtl.svg"
+ } },
+ "redirect": { "file": {
+ "ltr": "images/icons/redirect-ltr.svg",
+ "rtl": "images/icons/redirect-rtl.svg"
+ } },
+ "noWikiText": { "file": {
+ "ltr": "images/icons/noWikiText-ltr.svg",
+ "rtl": "images/icons/noWikiText-rtl.svg"
+ } },
+ "outline": { "file": {
+ "ltr": "images/icons/outline-ltr.svg",
+ "rtl": "images/icons/outline-rtl.svg"
+ } },
+ "puzzle": { "file": {
+ "ltr": "images/icons/puzzle-ltr.svg",
+ "rtl": "images/icons/puzzle-rtl.svg"
+ } },
+ "quotes": { "file": {
+ "ltr": "images/icons/quotes-ltr.svg",
+ "rtl": "images/icons/quotes-rtl.svg"
+ } },
+ "quotesAdd": { "file": {
+ "ltr": "images/icons/quotesAdd-ltr.svg",
+ "rtl": "images/icons/quotesAdd-rtl.svg"
+ } },
+ "redirect": { "file": {
+ "ltr": "images/icons/redirect-ltr.svg",
+ "rtl": "images/icons/redirect-rtl.svg"
+ } },
+ "searchCaseSensitive": { "file": "images/icons/case-sensitive.svg" },
+ "searchRegularExpression": { "file": "images/icons/regular-expression.svg" },
+ "specialCharacter": { "file": "images/icons/specialCharacter.svg" },
+ "table": { "file": "images/icons/table.svg" },
+ "tableAddColumnAfter": { "file": {
+ "ltr": "images/icons/table-insert-column-rtl.svg",
+ "rtl": "images/icons/table-insert-column-ltr.svg"
+ } },
+ "tableAddColumnBefore": { "file": {
+ "ltr": "images/icons/table-insert-column-ltr.svg",
+ "rtl": "images/icons/table-insert-column-rtl.svg"
+ } },
+ "tableAddRowAfter": { "file": "images/icons/table-insert-row-after.svg" },
+ "tableAddRowBefore": { "file": "images/icons/table-insert-row-before.svg" },
+ "tableCaption": { "file": "images/icons/table-caption.svg" },
+ "tableMergeCells": { "file": "images/icons/table-merge-cells.svg" },
+ "templateAdd": { "file": {
+ "ltr": "images/icons/templateAdd-ltr.svg",
+ "rtl": "images/icons/templateAdd-rtl.svg"
+ } },
+ "translation": { "file": {
+ "ltr": "images/icons/translation-ltr.svg",
+ "rtl": "images/icons/translation-rtl.svg"
+ } },
+ "wikiText": { "file": "images/icons/wikiText.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-core.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-core.json
new file mode 100644
index 00000000..3bacb605
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-core.json
@@ -0,0 +1,45 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "variants": {
+ "invert": {
+ "color": "#FFFFFF",
+ "global": true
+ },
+ "progressive": {
+ "color": "#347BFF"
+ },
+ "constructive": {
+ "color": "#00AF89"
+ },
+ "destructive": {
+ "color": "#D11D13"
+ },
+ "warning": {
+ "color": "#FF5D00"
+ }
+ },
+ "images": {
+ "edit": { "file": {
+ "ltr": "images/icons/edit-ltr.svg",
+ "rtl": "images/icons/edit-rtl.svg"
+ }, "variants": [ "progressive" ] },
+ "editLock": { "file": {
+ "ltr": "images/icons/editLock-ltr.svg",
+ "rtl": "images/icons/editLock-rtl.svg"
+ } },
+ "editUndo": { "file": {
+ "ltr": "images/icons/editUndo-ltr.svg",
+ "rtl": "images/icons/editUndo-rtl.svg"
+ } },
+ "link": { "file": {
+ "ltr": "images/icons/link-ltr.svg",
+ "rtl": "images/icons/link-rtl.svg"
+ } },
+ "linkExternal": { "file": {
+ "ltr": "images/icons/external-link-ltr.svg",
+ "rtl": "images/icons/external-link-rtl.svg"
+ } },
+ "linkSecure": { "file": "images/icons/secure-link.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-list.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-list.json
new file mode 100644
index 00000000..490f8faf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-list.json
@@ -0,0 +1,22 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "indent": { "file": {
+ "ltr": "images/icons/indent-ltr.svg",
+ "rtl": "images/icons/indent-rtl.svg"
+ } },
+ "listBullet": { "file": {
+ "ltr": "images/icons/listBullet-ltr.svg",
+ "rtl": "images/icons/listBullet-rtl.svg"
+ } },
+ "listNumbered": { "file": {
+ "ltr": "images/icons/listNumbered-ltr.svg",
+ "rtl": "images/icons/listNumbered-rtl.svg"
+ } },
+ "outdent": { "file": {
+ "ltr": "images/icons/outdent-ltr.svg",
+ "rtl": "images/icons/outdent-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-styling.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-styling.json
new file mode 100644
index 00000000..65fbc218
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-editing-styling.json
@@ -0,0 +1,72 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "bigger": { "file": {
+ "ltr": "images/icons/bigger-ltr.svg",
+ "rtl": "images/icons/bigger-rtl.svg"
+ } },
+ "smaller": { "file": {
+ "ltr": "images/icons/smaller-ltr.svg",
+ "rtl": "images/icons/smaller-rtl.svg"
+ } },
+ "subscript": { "file": {
+ "ltr": "images/icons/subscript-ltr.svg",
+ "rtl": "images/icons/subscript-rtl.svg"
+ } },
+ "superscript": { "file": {
+ "ltr": "images/icons/superscript-ltr.svg",
+ "rtl": "images/icons/superscript-rtl.svg"
+ } },
+ "bold": { "file": {
+ "default": "images/icons/bold-a.svg",
+ "lang": {
+ "ar": "images/icons/bold-arab-ain.svg",
+ "be": "images/icons/bold-cyrl-te.svg",
+ "cs,en,he,ml,pl": "images/icons/bold-b.svg",
+ "da,de,hu,ksh,nn,no,sv": "images/icons/bold-f.svg",
+ "es,gl,pt": "images/icons/bold-n.svg",
+ "eu,fi": "images/icons/bold-l.svg",
+ "fa": "images/icons/bold-arab-dad.svg",
+ "fr,it": "images/icons/bold-g.svg",
+ "hy": "images/icons/bold-armn-to.svg",
+ "ka": "images/icons/bold-geor-man.svg",
+ "ky,ru": "images/icons/bold-cyrl-zhe.svg",
+ "nl": "images/icons/bold-v.svg",
+ "os": "images/icons/bold-cyrl-be.svg"
+ }
+ } },
+ "italic": { "file": {
+ "default": "images/icons/italic-a.svg",
+ "lang": {
+ "ar": "images/icons/italic-arab-meem.svg",
+ "cs,en,fr,he,ml,pl,pt": "images/icons/italic-i.svg",
+ "be,da,de,fi,ky,nn,no,os,sv,ru": "images/icons/italic-k.svg",
+ "es,gl,it,nl": "images/icons/italic-c.svg",
+ "eu": "images/icons/italic-e.svg",
+ "fa": "images/icons/italic-arab-keheh-jeem.svg",
+ "hu": "images/icons/italic-d.svg",
+ "hy": "images/icons/italic-armn-sha.svg",
+ "ksh": "images/icons/italic-s.svg",
+ "ka": "images/icons/italic-geor-kan.svg"
+ }
+ } },
+ "strikethrough": { "file": {
+ "default": "images/icons/strikethrough-a.svg",
+ "lang": {
+ "en": "images/icons/strikethrough-s.svg",
+ "fi": "images/icons/strikethrough-y.svg"
+ }
+ } },
+ "underline": { "file": {
+ "default": "images/icons/underline-a.svg",
+ "lang": {
+ "en": "images/icons/underline-u.svg"
+ }
+ } },
+ "textLanguage": { "file": "images/icons/language.svg" },
+ "textDirLTR": { "file": "images/icons/text-dir-lefttoright.svg" },
+ "textDirRTL": { "file": "images/icons/text-dir-righttoleft.svg" },
+ "textStyle": { "file": "images/icons/text-style.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-interactions.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-interactions.json
new file mode 100644
index 00000000..497a3014
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-interactions.json
@@ -0,0 +1,52 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "beta": { "file": "images/icons/beta.svg" },
+ "betaLaunch": { "file": "images/icons/betaLaunch.svg" },
+ "bookmark": { "file": {
+ "ltr": "images/icons/bookmark-ltr.svg",
+ "rtl": "images/icons/bookmark-rtl.svg"
+ } },
+ "browser": { "file": {
+ "ltr": "images/icons/browser-ltr.svg",
+ "rtl": "images/icons/browser-rtl.svg"
+ } },
+ "clear": { "file": "images/icons/clear.svg" },
+ "clock": { "file": "images/icons/clock.svg" },
+ "funnel": { "file": {
+ "ltr": "images/icons/funnel-ltr.svg",
+ "rtl": "images/icons/funnel-rtl.svg"
+ } },
+ "heart": { "file": "images/icons/heart.svg" },
+ "key": { "file": {
+ "ltr": "images/icons/key-ltr.svg",
+ "rtl": "images/icons/key-rtl.svg"
+ } },
+ "keyboard": { "file": {
+ "ltr": "images/icons/keyboard-ltr.svg",
+ "rtl": "images/icons/keyboard-rtl.svg"
+ } },
+ "logOut": { "file": {
+ "ltr": "images/icons/logOut-ltr.svg",
+ "rtl": "images/icons/logOut-rtl.svg"
+ } },
+ "newWindow": { "file": {
+ "ltr": "images/icons/newWindow-ltr.svg",
+ "rtl": "images/icons/newWindow-rtl.svg"
+ } },
+ "printer": { "file": {
+ "ltr": "images/icons/printer-ltr.svg",
+ "rtl": "images/icons/printer-rtl.svg"
+ } },
+ "ribbonPrize": { "file": "images/icons/ribbonPrize.svg" },
+ "sun": { "file": {
+ "ltr": "images/icons/sun-ltr.svg",
+ "rtl": "images/icons/sun-rtl.svg"
+ } },
+ "watchlist": { "file": {
+ "ltr": "images/icons/watchlist-ltr.svg",
+ "rtl": "images/icons/watchlist-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-layout.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-layout.json
new file mode 100644
index 00000000..ae6b09d7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-layout.json
@@ -0,0 +1,43 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "variants": {
+ "invert": {
+ "color": "#FFFFFF",
+ "global": true
+ },
+ "progressive": {
+ "color": "#347BFF"
+ },
+ "constructive": {
+ "color": "#00AF89"
+ },
+ "destructive": {
+ "color": "#D11D13"
+ },
+ "warning": {
+ "color": "#FF5D00"
+ }
+ },
+ "images": {
+ "stripeFlow": { "file": {
+ "ltr": "images/icons/stripeFlow-ltr.svg",
+ "rtl": "images/icons/stripeFlow-rtl.svg"
+ } },
+ "stripeSideMenu": { "file": "images/icons/stripeSideMenu.svg" },
+ "stripeSummary": { "file": {
+ "ltr": "images/icons/stripeSummary-ltr.svg",
+ "rtl": "images/icons/stripeSummary-rtl.svg"
+ } },
+ "stripeToC": { "file": {
+ "ltr": "images/icons/stripeToC-ltr.svg",
+ "rtl": "images/icons/stripeToC-rtl.svg"
+ }, "variants": [ "progressive" ] },
+ "viewCompact": { "file": "images/icons/viewCompact.svg" },
+ "viewDetails": { "file": {
+ "ltr": "images/icons/viewDetails-ltr.svg",
+ "rtl": "images/icons/viewDetails-rtl.svg"
+ } },
+ "visionSimulator": { "file": "images/icons/visionSimulator.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-location.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-location.json
new file mode 100644
index 00000000..9eec19ba
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-location.json
@@ -0,0 +1,19 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "map": { "file": {
+ "ltr": "images/icons/map-ltr.svg",
+ "rtl": "images/icons/map-rtl.svg"
+ } },
+ "mapPin": { "file": "images/icons/mapPin.svg" },
+ "mapPinAdd": { "file": {
+ "ltr": "images/icons/mapPinAdd-ltr.svg",
+ "rtl": "images/icons/mapPinAdd-rtl.svg"
+ } },
+ "wikitrail": { "file": {
+ "ltr": "images/icons/wikitrail-ltr.svg",
+ "rtl": "images/icons/wikitrail-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-media.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-media.json
new file mode 100644
index 00000000..960079b4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-media.json
@@ -0,0 +1,27 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "image": { "file": {
+ "ltr": "images/icons/image-ltr.svg",
+ "rtl": "images/icons/image-rtl.svg"
+ } },
+ "imageAdd": { "file": {
+ "ltr": "images/icons/imageAdd-ltr.svg",
+ "rtl": "images/icons/imageAdd-rtl.svg"
+ } },
+ "imageLock": { "file": {
+ "ltr": "images/icons/imageLock-ltr.svg",
+ "rtl": "images/icons/imageLock-rtl.svg"
+ } },
+ "photoGallery": { "file": {
+ "ltr": "images/icons/photoGallery-ltr.svg",
+ "rtl": "images/icons/photoGallery-rtl.svg"
+ } },
+ "play": { "file": {
+ "ltr": "images/icons/play-ltr.svg",
+ "rtl": "images/icons/play-rtl.svg"
+ } },
+ "stop": { "file": "images/icons/stop.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-moderation.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-moderation.json
new file mode 100644
index 00000000..1f12f2ae
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-moderation.json
@@ -0,0 +1,52 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "variants": {
+ "invert": {
+ "color": "#FFFFFF",
+ "global": true
+ },
+ "progressive": {
+ "color": "#347BFF"
+ },
+ "constructive": {
+ "color": "#00AF89"
+ },
+ "destructive": {
+ "color": "#D11D13"
+ },
+ "warning": {
+ "color": "#FF5D00"
+ }
+ },
+ "images": {
+ "block": { "file": "images/icons/block.svg", "variants": [ "destructive" ] },
+ "blockUndo": { "file": {
+ "ltr": "images/icons/blockUndo-ltr.svg",
+ "rtl": "images/icons/blockUndo-rtl.svg"
+ } },
+ "flag": { "file": {
+ "ltr": "images/icons/flag-ltr.svg",
+ "rtl": "images/icons/flag-rtl.svg"
+ } },
+ "flagUndo": { "file": {
+ "ltr": "images/icons/flagUndo-ltr.svg",
+ "rtl": "images/icons/flagUndo-rtl.svg"
+ } },
+ "lock": { "file": {
+ "ltr": "images/icons/lock-ltr.svg",
+ "rtl": "images/icons/lock-rtl.svg"
+ }, "variants": [ "destructive" ] },
+ "star": { "file": "images/icons/star.svg" },
+ "trash": { "file": "images/icons/trash.svg" },
+ "trashUndo": { "file": {
+ "ltr": "images/icons/trashUndo-ltr.svg",
+ "rtl": "images/icons/trashUndo-rtl.svg"
+ } },
+ "unLock": { "file": {
+ "ltr": "images/icons/unLock-ltr.svg",
+ "rtl": "images/icons/unLock-rtl.svg"
+ }, "variants": [ "destructive" ] },
+ "unStar": { "file": "images/icons/unStar.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-movement.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-movement.json
new file mode 100644
index 00000000..9aa1b809
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-movement.json
@@ -0,0 +1,27 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "arrowNext": { "file": {
+ "ltr": "images/icons/arrow-ltr.svg",
+ "rtl": "images/icons/arrow-rtl.svg"
+ } },
+ "arrowLast": { "file": {
+ "ltr": "images/icons/arrow-rtl.svg",
+ "rtl": "images/icons/arrow-ltr.svg"
+ } },
+ "caretNext": { "file": {
+ "ltr": "images/icons/caret-rtl.svg",
+ "rtl": "images/icons/caret-ltr.svg"
+ } },
+ "caretLast": { "file": {
+ "ltr": "images/icons/caret-ltr.svg",
+ "rtl": "images/icons/caret-rtl.svg"
+ } },
+ "caretDown": { "file": "images/icons/caretDown.svg" },
+ "caretUp": { "file": "images/icons/caretUp.svg" },
+ "downTriangle": { "file": "images/icons/downTriangle.svg" },
+ "move": { "file": "images/icons/move.svg" },
+ "upTriangle": { "file": "images/icons/upTriangle.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-user.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-user.json
new file mode 100644
index 00000000..2bda5753
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-user.json
@@ -0,0 +1,19 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "userActive": { "file": {
+ "ltr": "images/icons/userActive-ltr.svg",
+ "rtl": "images/icons/userActive-rtl.svg"
+ } },
+ "userAvatar": { "file": "images/icons/userAvatar.svg" },
+ "userInactive": { "file": {
+ "ltr": "images/icons/userInactive-ltr.svg",
+ "rtl": "images/icons/userInactive-rtl.svg"
+ } },
+ "userTalk": { "file": {
+ "ltr": "images/icons/userTalk-ltr.svg",
+ "rtl": "images/icons/userTalk-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-wikimedia.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-wikimedia.json
new file mode 100644
index 00000000..fd13c95d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons-wikimedia.json
@@ -0,0 +1,9 @@
+{
+ "prefix": "oo-ui-icon",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "logoCC": { "file": "images/icons/logo-cc.svg" },
+ "logoWikimediaCommons": { "file": "images/icons/logo-wikimediaCommons.svg" },
+ "logoWikipedia": { "file": "images/icons/logo-wikipedia.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/icons.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons.json
new file mode 100644
index 00000000..d385eb11
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/icons.json
@@ -0,0 +1,75 @@
+{
+ "selectorWithoutVariant": ".oo-ui-icon-{name}",
+ "selectorWithVariant": ".oo-ui-image-{variant} .oo-ui-icon-{name}, .oo-ui-image-{variant}.oo-ui-icon-{name}",
+ "intro": "@import '../../../../src/styles/common';",
+ "variants": {
+ "invert": {
+ "color": "#FFFFFF",
+ "global": true
+ },
+ "progressive": {
+ "color": "#347BFF"
+ },
+ "constructive": {
+ "color": "#00AF89"
+ },
+ "destructive": {
+ "color": "#D11D13"
+ },
+ "warning": {
+ "color": "#FF5D00"
+ }
+ },
+ "images": {
+ "add": { "file": "images/icons/add.svg", "variants": [ "constructive" ] },
+ "advanced": { "file": "images/icons/advanced.svg" },
+ "alert": { "file": "images/icons/alert.svg", "variants": [ "warning" ] },
+ "cancel": { "file": "images/icons/cancel.svg" },
+ "check": { "file": "images/icons/check.svg", "variants": [ "constructive", "progressive" ] },
+ "circle": { "file": "images/icons/circle.svg", "variants": [ "constructive" ] },
+ "close": { "file": {
+ "ltr": "images/icons/close-ltr.svg",
+ "rtl": "images/icons/close-rtl.svg"
+ } },
+ "code": { "file": "images/icons/code.svg" },
+ "collapse": { "file": "images/icons/collapse.svg" },
+ "comment": { "file": "images/icons/comment.svg" },
+ "ellipsis": { "file": "images/icons/ellipsis.svg" },
+ "expand": { "file": "images/icons/expand.svg" },
+ "help": { "file": {
+ "ltr": "images/icons/help-ltr.svg",
+ "rtl": "images/icons/help-rtl.svg",
+ "lang": {
+ "he,yi": "images/icons/help-ltr.svg"
+ }
+ } },
+ "history": { "file": "images/icons/history.svg" },
+ "info": { "file": "images/icons/info.svg" },
+ "menu": { "file": "images/icons/menu.svg" },
+ "next": { "file": {
+ "ltr": "images/icons/move-ltr.svg",
+ "rtl": "images/icons/move-rtl.svg"
+ } },
+ "picture": { "file": "images/icons/picture.svg" },
+ "previous": { "file": {
+ "ltr": "images/icons/move-rtl.svg",
+ "rtl": "images/icons/move-ltr.svg"
+ } },
+ "redo": { "file": {
+ "ltr": "images/icons/arched-arrow-ltr.svg",
+ "rtl": "images/icons/arched-arrow-rtl.svg"
+ } },
+ "remove": { "file": "images/icons/remove.svg", "variants": [ "destructive" ] },
+ "search": { "file": {
+ "ltr": "images/icons/search-ltr.svg",
+ "rtl": "images/icons/search-rtl.svg"
+ } },
+ "settings": { "file": "images/icons/settings.svg" },
+ "tag": { "file": "images/icons/tag.svg", "variants": [ "destructive", "warning", "constructive", "progressive" ] },
+ "undo": { "file": {
+ "ltr": "images/icons/arched-arrow-rtl.svg",
+ "rtl": "images/icons/arched-arrow-ltr.svg"
+ } },
+ "window": { "file": "images/icons/window.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/add.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/add.svg
new file mode 100644
index 00000000..29e5dba8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/add.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="add">
+ <path id="plus" d="M13 8h-2v3h-3v2h3v3h2v-3h3v-2h-3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/advanced.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/advanced.svg
new file mode 100644
index 00000000..b4629bf9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/advanced.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M20 14.5v-2.9l-1.8-.3c-.1-.4-.3-.8-.6-1.4l1.1-1.5-2.1-2.1-1.5 1.1c-.5-.3-1-.5-1.4-.6l-.2-1.8h-2.9l-.3 1.8c-.5.1-.9.3-1.4.6l-1.5-1.1-2.1 2.1 1 1.5c-.3.5-.4.9-.6 1.4l-1.7.2v2.9l1.8.3c.1.5.3.9.6 1.4l-1 1.5 2.1 2.1 1.5-1c.4.2.9.4 1.4.6l.3 1.8h3l.3-1.8c.5-.1.9-.3 1.4-.6l1.5 1.1 2.1-2.1-1.1-1.5c.3-.5.5-1 .6-1.4l1.5-.3zm-8 1.5c-1.7 0-3-1.3-3-3s1.3-3 3-3 3 1.3 3 3-1.3 3-3 3z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/alert.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/alert.svg
new file mode 100644
index 00000000..f0c65224
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/alert.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="alert">
+ <path id="point" d="M11 16h2v2h-2z"/>
+ <path id="stroke" d="M13.516 10h-3l.484 5h2z"/>
+ <path id="triangle" d="M12.017 5.974l7.519 13.026h-15.04l7.521-13.026m0-2.474c-.544 0-1.088.357-1.5 1.071l-7.985 13.831c-.825 1.429-.15 2.598 1.5 2.598h15.968c1.65 0 2.325-1.169 1.5-2.599l-7.983-13.829c-.413-.715-.956-1.072-1.5-1.072z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-center.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-center.svg
new file mode 100644
index 00000000..887c2f66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-center.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="align-center">
+ <path d="M9 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1h-6c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zM3.5 18h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM3.5 6h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-left.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-left.svg
new file mode 100644
index 00000000..ce9761e2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-left.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="align-float-left">
+ <path d="M4 9h6c.554 0 1 .446 1 1v5c0 .554-.446 1-1 1h-6c-.554 0-1-.446-1-1v-5c0-.554.446-1 1-1zM13.5 9h7c.277 0 .5.223.5.5s-.223.5-.5.5h-7c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM13.5 12h7c.277 0 .5.223.5.5s-.223.5-.5.5h-7c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM13.5 15h7c.277 0 .5.223.5.5s-.223.5-.5.5h-7c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM3.5 6h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5zM3.5 18h17c.277 0 .5.223.5.5s-.223.5-.5.5h-17c-.277 0-.5-.223-.5-.5s.223-.5.5-.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-right.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-right.svg
new file mode 100644
index 00000000..557692ae
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/align-float-right.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="align-float-right">
+ <path d="M20 9h-6c-.554 0-1 .446-1 1v5c0 .554.446 1 1 1h6c.554 0 1-.446 1-1v-5c0-.554-.446-1-1-1zM10.5 9h-7c-.277 0-.5.223-.5.5s.223.5.5.5h7c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM10.5 12h-7c-.277 0-.5.223-.5.5s.223.5.5.5h7c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM10.5 15h-7c-.277 0-.5.223-.5.5s.223.5.5.5h7c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM20.5 6h-17c-.277 0-.5.223-.5.5s.223.5.5.5h17c.277 0 .5-.223.5-.5s-.223-.5-.5-.5zM20.5 18h-17c-.277 0-.5.223-.5.5s.223.5.5.5h17c.277 0 .5-.223.5-.5s-.223-.5-.5-.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-ltr.svg
new file mode 100644
index 00000000..049f21e2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-487 489 24 24" enable-background="new -487 489 24 24" xml:space="preserve">
+<path d="M-472.8,494.7l6.3,5.7l-6.3,5.7v-3.8h-1.3c-3.2,0-6.3,1.3-7.6,3.8c0-4.7,2.8-7.6,7.9-7.6h0.9V494.7z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-rtl.svg
new file mode 100644
index 00000000..20875f34
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arched-arrow-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-487 489 24 24" enable-background="new -487 489 24 24" xml:space="preserve">
+<path d="M-476.3,494.7l-6.3,5.7l6.3,5.7v-3.8h1.3c3.2,0,6.3,1.3,7.6,3.8c0-4.7-2.8-7.6-7.9-7.6h-0.9V494.7z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-ltr.svg
new file mode 100644
index 00000000..b07621e8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M16 12h-10c-1.7 0-3 1.3-3 3h13v3l5-4.5-5-4.5v3z" id="path6"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-rtl.svg
new file mode 100644
index 00000000..a0189283
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/arrow-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M8 12h10c1.7 0 3 1.3 3 3h-13v3l-5-4.5 5-4.5v3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-ltr.svg
new file mode 100644
index 00000000..b719946d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M12 10h4v-5h-4v5zm-5 2h9v-1h-9v1zm0 2h9v-1h-9v1zm0 2h9v-1h-9v1zm4-9h-4v1h4v-1zm0 2h-4v1h4v-1zm0-4h-4v1h4v-1zm-6-2h13v16h-10c-1.7 0-3-1.3-3-3v-13z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-rtl.svg
new file mode 100644
index 00000000..f14dfbda
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/article-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g16">
+ <path d="M11 10h-4v-5h4v5zm5 2h-9v-1h9v1zm0 2h-9v-1h9v1zm0 2h-9v-1h9v1zm-4-9h4v1h-4v-1zm0 2h4v1h-4v-1zm0-4h4v1h-4v-1zm6-2h-13v16h10c1.7 0 3-1.3 3-3v-13z" id="path18"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-ltr.svg
new file mode 100644
index 00000000..77119710
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-ltr.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <g>
+ <path d="M21 11l-6 7-4-4-1 1 5 5 7-8z"/>
+ </g>
+ <path d="M17 14v-11h-13v13c0 1.7 1.3 3 3 3h5l-3-3h-3v-1h2.6l1-1h-3.6v-1h9v1h-2l1 1h2l1-1zm-11-9h4v1h-4v-1zm0 2h4v1h-4v-1zm0 2h4v1h-4v-1zm9 3h-9v-1h9v1zm-4-2v-5h4v5h-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-rtl.svg
new file mode 100644
index 00000000..771b3ffb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleCheck-rtl.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g28">
+ <g id="g30">
+ <path d="M5 11l6 7 4-4 1 1-5 5-7-8z" id="path32"/>
+ </g>
+ <path d="M9 14v-11h13v13c0 1.7-1.3 3-3 3h-5l3-3h3v-1h-2.6l-1-1h3.6v-1h-9v1h2l-1 1h-2l-1-1zm11-9h-4v1h4v-1zm0 2h-4v1h4v-1zm0 2h-4v1h4v-1zm-9 3h9v-1h-9v1zm4-2v-5h-4v5h4z" id="path34"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-ltr.svg
new file mode 100644
index 00000000..e54c0c4a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M19.1 18.5c.6-.7.9-1.5.9-2.5 0-2.2-1.8-4-4-4s-4 1.8-4 4 1.8 4 4 4c.7 0 1.3-.1 1.8-.4l2.7 2.7 1.1-1.1-2.5-2.7zm-3.1-.3c-1.2 0-2.2-1-2.2-2.3 0-1.2 1-2.2 2.2-2.2 1.2 0 2.3 1 2.3 2.2-.1 1.3-1.1 2.3-2.3 2.3zm-4.2-5.2c.3-.4.6-.7 1-1h-5.8v-1h9s1.2 0 2 .6v-8.6h-13v13c0 1.7 1.3 3 3 3h3.8c-.6-.8-1-1.9-1-3h-3.8v-1h3.9l.3-1h-4.2v-1h4.8zm.2-8h4v5h-4v-5zm-5 0h4v1h-4v-1zm0 2h4v1h-4v-1zm0 2h4v1h-4v-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-rtl.svg
new file mode 100644
index 00000000..31134f1c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/articleSearch-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g44">
+ <path d="M7.5 18.5c-.6-.7-.9-1.5-.9-2.5 0-2.2 1.8-4 4-4s4 1.8 4 4-1.8 4-4 4c-.7 0-1.3-.1-1.8-.4l-2.7 2.7-1.1-1.1 2.5-2.7zm3.1-.3c1.2 0 2.2-1 2.2-2.3 0-1.2-1-2.2-2.2-2.2-1.2 0-2.3 1-2.3 2.2.1 1.3 1.1 2.3 2.3 2.3zm4.2-5.2c-.3-.4-.6-.7-1-1h5.8v-1h-9s-1.2 0-2 .6v-8.6h13v13c0 1.7-1.3 3-3 3h-3.8c.6-.8 1-1.9 1-3h3.8v-1h-3.9l-.3-1h4.2v-1h-4.8zm-.2-8h-4v5h4v-5zm5 0h-4v1h4v-1zm0 2h-4v1h4v-1zm0 2h-4v1h4v-1z" id="path46"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bell.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bell.svg
new file mode 100644
index 00000000..df08800f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bell.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M17.5 14v-5c0-3-2.3-5-5.5-5s-5.5 2-5.5 5v5c0 2 0 3-2 3v1h15v-1c-2 0-2-1-2-3zm-5.5 6h-3c0 1 1.6 2 3 2s3-1 3-2h-3z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-ltr.svg
new file mode 100644
index 00000000..f419e79f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M17.8 14.7l1.7-4.7c1-2.8-.5-5.5-3.5-6.6s-5.9 0-6.9 2.8l-1.7 4.7c-.7 1.9-1 2.8-2.9 2.1l-.3 1 14.1 5.1.3-.9c-1.9-.7-1.5-1.6-.8-3.5zm-5.8 5.1l-2.8-1c-.3.9.8 2.4 2.1 2.9s3.2.1 3.5-.9l-2.8-1z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-rtl.svg
new file mode 100644
index 00000000..e4c3a3fa
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bellOn-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M6.209 14.7l-1.7-4.7c-1-2.8.5-5.5 3.5-6.6 3-1.1 5.9 0 6.9 2.8l1.7 4.7c.7 1.9 1 2.8 2.9 2.1l.3 1-14.1 5.1-.3-.9c1.9-.7 1.5-1.6.8-3.5zm5.8 5.1l2.8-1c.3.9-.8 2.4-2.1 2.9s-3.2.1-3.5-.9l2.8-1z" id="path56"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/beta.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/beta.svg
new file mode 100644
index 00000000..51a5c782
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/beta.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 12l-3-2-1 4-1-4-3 2 2-3-4-1 4-1-2-3 3 2 1-4 1 4 3-2-2 3 4 1-4 1 2 3z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/betaLaunch.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/betaLaunch.svg
new file mode 100644
index 00000000..a693b59b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/betaLaunch.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M15.3 14.7c.8-3.8-.6-10.7-3.3-10.7-2.7 0-4.2 6.7-3.4 10.5l-1.6 3.5h2.7l.3 1h4c.2-.3.1-.5.3-1h2.7l-1.7-3.3zm-3.3-4.7c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm2 10c0 1.1-2 2-2 2s-2-.9-2-2"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-ltr.svg
new file mode 100644
index 00000000..94ec6704
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12.666 6h-1.372l-4.48 12h1.705l1.494-4h3.999l1.508 4h1.666l-4.52-12zm-2.28 7l1.617-4.333 1.634 4.333h-3.251z" id="a"/>
+ <g id="up">
+ <path id="arrow" d="M15.5 9h7l-3.5-6z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-rtl.svg
new file mode 100644
index 00000000..b2a6c139
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bigger-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z" id="a"/>
+ <g id="up">
+ <path id="arrow" d="M1.5 9h7L5 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/block.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/block.svg
new file mode 100644
index 00000000..0ddd1d47
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/block.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 4c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm5 9h-10v-2h10v2z" id="path4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-ltr.svg
new file mode 100644
index 00000000..3d9cfd7d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g66">
+ <path d="M17 11v2h-2l3.6 3.6c.9-1.3 1.4-2.9 1.4-4.6 0-4.4-3.6-8-8-8-1.7 0-3.3.5-4.6 1.4l5.6 5.6h4zm-13-7l-1 1 2.4 2.4c-.9 1.3-1.4 2.9-1.4 4.6 0 4.4 3.6 8 8 8 1.7 0 3.3-.5 4.6-1.4l2.4 2.4 1-1-16-16zm3 9v-2h2l2 2h-4z" id="path68"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-rtl.svg
new file mode 100644
index 00000000..8f807596
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/blockUndo-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g66">
+ <path d="M7 11v2h2l-3.6 3.6c-.9-1.3-1.4-2.9-1.4-4.6 0-4.4 3.6-8 8-8 1.7 0 3.3.5 4.6 1.4l-5.6 5.6h-4zm13-7l1 1-2.4 2.4c.9 1.3 1.4 2.9 1.4 4.6 0 4.4-3.6 8-8 8-1.7 0-3.3-.5-4.6-1.4l-2.4 2.4-1-1 16-16zm-3 9v-2h-2l-2 2h4z" id="path68"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-a.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-a.svg
new file mode 100644
index 00000000..4b828779
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-a.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-a">
+ <path d="M16 18h3l-5-12h-3l-5 12h3l1.25-3h4.5l1.25 3zm-4.917-5l1.417-3.4 1.417 3.4h-2.834z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-ain.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-ain.svg
new file mode 100644
index 00000000..f96cebce
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-ain.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-arab-ain">
+ <path id="arab-ain" d="M9.337 13.616c0 1.349 1.386 2.101 4.159 2.258l2.187-.029.318.044c-.03.127-.251.345-.665.652l-.089.066c-1.236.929-2.423 1.393-3.56 1.393-1.143 0-2.046-.33-2.711-.99-.65-.66-.975-1.559-.975-2.698.005-1.354.566-2.573 1.684-3.658v-.044l-.606-.55c-.148-.181-.222-.391-.222-.63 0-.489.239-1.109.717-1.862.65-1.046 1.303-1.566 1.958-1.561.886.005 1.618.42 2.194 1.246.325.479-.03.552-1.064.22-.842-.327-1.527-.051-2.054.828l.015.073 1.123.865.052.007c1.404-.498 2.418-.74 3.043-.726-.059.117-.14.362-.244.733-.103.357-.204.684-.303.982l-.126.374-.384.051c-1.743.239-2.992.716-3.745 1.429-.463.464-.697.973-.702 1.525"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-dad.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-dad.svg
new file mode 100644
index 00000000..f04c6aad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-arab-dad.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-arab-dad">
+ <path id="arab-dad" d="M16.411 8.232l-1.676-.665.694-1.567 1.688.64-.707 1.592m.775 3.078c-.509-.286-1-.427-1.476-.423-.471 0-.982.205-1.532.616l-.506.379.006.025c1.084.066 1.934.099 2.551.099h.313c.567-.021.992-.064 1.276-.131-.067-.17-.275-.359-.625-.566h-.006m-6.803 3.296c-.017-.904-.329-1.87-.938-2.898l1.294-1.729.119.149c.267.336.504.924.713 1.766l.063.05c.496-.008.942-.17 1.338-.485v-.006l1.732-1.53c.679-.601 1.282-.902 1.807-.902.383.004.848.195 1.394.572.55.377.884.696 1 .958.063.149.094.386.094.709 0 .696-.11 1.229-.331 1.598-.192.311-.473.555-.844.734-.438.207-1.549.311-3.333.311-.8 0-1.795-.021-2.983-.062l-.144.429c-.254.672-.463 1.113-.625 1.324-.725.937-1.786 1.405-3.183 1.405-1.705-.008-2.557-.922-2.557-2.742.004-.941.279-1.814.825-2.618.15-.216.298-.367.444-.454.225-.133.288-.091.188.124-.396.862-.596 1.548-.6 2.058.008 1.177.752 1.768 2.232 1.772 1.038-.004 1.803-.182 2.295-.535"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-armn-to.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-armn-to.svg
new file mode 100644
index 00000000..4dbec6d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-armn-to.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-armn-to">
+ <path id="armn-to" d="M13.86 16.257c.124 0 .254-.026.39-.078.135-.058.257-.15.367-.274.114-.13.205-.302.273-.516.073-.213.11-.48.11-.797V13h-1.14c-.14 0-.284.026-.43.078-.14.047-.27.133-.383.258-.11.125-.2.294-.274.508-.067.213-.1.487-.1.82 0 .34.035.47.108.695.08.218.175.395.29.53.12.136.247.232.383.29.14.05.276.077.406.077m-2.97-7.84c-.37.082-.695.247-.976.45-.28.198-.505.47-.672.813-.16.343-.242.78-.242 1.312V18H6v-7.188c0-.776.15-1.455.453-2.04.302-.587.714-1.077 1.234-1.467.52-.39 1.13-.685 1.83-.883.697-.198 1.44-.297 2.225-.297.526 0 1.04.044 1.54.133.504.088.98.22 1.43.398.447.172.858.388 1.233.65.375.26.698.564.97.913.275.348.49.738.64 1.17.15.433.226 1.094.226 1.61h1.353v2.04H17.78v1.6c0 .58-.103 1.092-.31 1.54-.21.442-.49.815-.845 1.117-.35.302-.834.53-1.297.687-.464.15-.953.226-1.47.226-.51 0-.996-.078-1.46-.234-.464-.156-.87-.39-1.22-.703-.348-.313-.626-.703-.835-1.172-.203-.473-.304-1.028-.304-1.663s.105-1.182.32-1.64c.213-.46.497-.685.85-.977.355-.297.76-.513 1.22-.648.458-.14.935-.21 1.43-.21h1.132c-.01-.49-.04-1.043-.242-1.36-.198-.323-.453-.58-.766-.766-.312-.193-.598-.332-.984-.426-.374-.09-.577-.094-1.1-.094-.52 0-.64.02-1.01.102z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-b.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-b.svg
new file mode 100644
index 00000000..4f648203
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-b.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-b">
+ <path id="b" d="M7 18h6c2 0 4-1 4-3 0-1.064.011-1.975-1.989-3 2-.975 1.989-1.935 1.989-3 0-2-2-3-4-3h-6v12zm7-8c0 1.001 0 1-2 1h-2v-3h2c2 0 2 0 2 1v1zm-2 6h-2v-3h2c2 0 2 0 2 1v1s0 1-2 1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-be.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-be.svg
new file mode 100644
index 00000000..279466d4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-be.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-cyrl-be">
+ <path id="cyrl-be" d="M7 6h9v2h-6v3h2.649c.893 0 1.633.109 2.22.327.588.218 1.088.622 1.502 1.211.419.589.629 1.187.629 1.978 0 .813-.21 1.398-.629 1.977-.419.578-.898.974-1.437 1.187-.533.213-1.295.319-2.286.319h-5.649m4.767-2c.751 0 1.279-.049 1.584-.12.305-.076.569-.246.792-.508.229-.262.343-.473.343-.855 0-.557-.199-.868-.596-1.119-.392-.256-1.064-.398-2.016-.398h-1.873v3"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-te.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-te.svg
new file mode 100644
index 00000000..fdeeb6c5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-te.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-cyrl-te">
+ <path id="te" d="M11 18v-10h-4v-2h11v2h-4v10"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-zhe.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-zhe.svg
new file mode 100644
index 00000000..5996c813
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-cyrl-zhe.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-cyrl-zhe">
+ <path id="cyrl-zhe" d="M13 6v5.154c.328-.033.537-.181.705-.447.168-.266.401-.873.698-1.821.39-1.241.789-2.033 1.197-2.374.403-.336 1.075-.504 2.014-.504l.386-.008v1.78l-.386-.008c-.399 0-.691.062-.878.187-.186.119-.337.304-.452.553-.115.249-.286.762-.512 1.537-.12.412-.25.756-.392 1.033-.137.276-.383.537-.738.78.439.157.8.466 1.084.927.288.455.603 1.103.944 1.943l1.33 3.268h-2.314l-1.17-3.081-.113-.252-.239-.561c-.248-.569-.452-.932-.612-1.089-.16-.157-.317-.236-.552-.236v5.22h-2v-5.22c-.226 0-.382.076-.546.228-.164.152-.368.518-.612 1.098l-.246.561-.113.252-1.17 3.081h-2.314l1.33-3.268c.328-.808.636-1.447.924-1.919.293-.477.663-.794 1.11-.951-.355-.244-.603-.501-.745-.772-.137-.276-.268-.623-.392-1.041-.222-.759-.39-1.266-.505-1.52-.111-.255-.261-.444-.452-.569-.186-.125-.492-.187-.917-.187l-.352.008v-1.78l.386.008c.953 0 1.631.171 2.034.512.399.347.791 1.136 1.177 2.366.301.954.534 1.564.698 1.829.168.26.377.406.705.439v-5.154"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-f.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-f.svg
new file mode 100644
index 00000000..357d2e5d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-f.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-f">
+ <path id="f" d="M16 8v-2h-8v12h3v-5h4v-2h-4v-3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-g.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-g.svg
new file mode 100644
index 00000000..e032542e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-g.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-g">
+ <path id="g" d="M12 14v-2h5v4.203c-.497.475-1.22.894-2.166 1.259-.941.359-1.896.538-2.864.538-1.23 0-2.303-.253-3.217-.76-.915-.512-1.602-1.24-2.062-2.185-.46-.95-.69-1.982-.69-3.095 0-1.208.257-2.282.77-3.222.513-.939 1.265-1.66 2.255-2.161.754-.385 1.693-.578 2.816-.578 1.46 0 2.6.303 3.418.91.824.602 1.353 1.435 1.589 2.501l-2.359.435c-.166-.57-.479-1.018-.939-1.346-.455-.332-1.024-.499-1.709-.499-1.038 0-1.864.325-2.479.974-.61.649-.915 1.612-.915 2.889 0 1.377.31 2.412.931 3.103.62.686 1.433 1.029 2.439 1.029.497 0 .995-.095 1.492-.285.503-.195 1.332-.571 1.691-.845v-.867"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-geor-man.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-geor-man.svg
new file mode 100644
index 00000000..b211bf7a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-geor-man.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-geor-man">
+ <path id="geor-man" d="M13.832 14.061c0-1.715-.394-2.573-1.182-2.573-.868 0-1.302.779-1.302 2.338-.01 1.624.421 2.436 1.295 2.436.793 0 1.189-.734 1.189-2.201m2.168 0c0 2.626-1.116 3.939-3.349 3.939-2.434 0-3.651-1.386-3.651-4.159 0-2.738 1.217-4.106 3.651-4.106.841 0 1.182.63 1.182.63v-1.579c0-.789-.449-1.184-1.347-1.184-.572 0-.858.374-.858 1.123h-2.341c.005-1.817 1.064-2.725 3.176-2.725 2.368 0 3.548.946 3.538 2.839"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-l.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-l.svg
new file mode 100644
index 00000000..16797938
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-l.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-l">
+ <path id="l" d="M8 18v-12h3v10h5v2"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-n.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-n.svg
new file mode 100644
index 00000000..73ad019a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-n.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-n">
+ <path id="n" d="M7 18v-12h3l4 8v-8h3v12h-3l-4-8v8h-3"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-v.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-v.svg
new file mode 100644
index 00000000..146943a5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bold-v.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="bold-v">
+ <path id="v" d="M10.5 18l-4.5-12h3l3 8 3-8h3l-4.5 12"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-ltr.svg
new file mode 100644
index 00000000..7a058ed3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M15 7c-1.7 0-3 1.3-3 3 0-1.7-1.3-3-3-3h-6v13h6c1.7 0 3 1 3 2 0-1 1.3-2 3-2h6v-13h-6zm5 12h-5c-1.7 0-2 .4-2 .4v-8.9c0-1.4 1.1-2.5 2.5-2.5h4.5v11z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-rtl.svg
new file mode 100644
index 00000000..6ae47ec5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/book-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M9 7c1.7 0 3 1.3 3 3 0-1.7 1.3-3 3-3h6v13h-6c-1.7 0-3 1-3 2 0-1-1.3-2-3-2h-6v-13h6zm-5 12h5c1.7 0 2 .4 2 .4v-8.9c0-1.4-1.1-2.5-2.5-2.5h-4.5v11z" id="path78"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-ltr.svg
new file mode 100644
index 00000000..d803d6be
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M15 5h-7c-1.1 0-2 .9-2 2v3h3v11l4-3 4 3v-14c0-1.1-.9-2-2-2zm-6 4h-2v-2c0-.6.4-1 1-1h1v3z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-rtl.svg
new file mode 100644
index 00000000..744d0f4e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/bookmark-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M8 5h7c1.1 0 2 .9 2 2v3h-3v11l-4-3-4 3v-14c0-1.1.9-2 2-2zm6 4h2v-2c0-.6-.4-1-1-1h-1v3z" id="path88"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-ltr.svg
new file mode 100644
index 00000000..7bd04250
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M3 6v11c0 1.7 1.3 3 3 3h15v-14h-18zm2.5 1c.8 0 1.5.7 1.5 1.5s-.7 1.5-1.5 1.5-1.5-.7-1.5-1.5.7-1.5 1.5-1.5zm14.5 12h-14c-1.1 0-2-.9-2-2v-6h16v8z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-rtl.svg
new file mode 100644
index 00000000..84b18dae
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/browser-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M21 6v11c0 1.7-1.3 3-3 3h-15v-14h18zm-2.5 1c-.8 0-1.5.7-1.5 1.5s.7 1.5 1.5 1.5 1.5-.7 1.5-1.5-.7-1.5-1.5-1.5zm-14.5 12h14c1.1 0 2-.9 2-2v-6h-16v8z" id="path98"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/cancel.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/cancel.svg
new file mode 100644
index 00000000..bfc1b44b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/cancel.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="cancel">
+ <path id="circle-with-strike" d="M11.999 5.022c-3.853 0-6.977 3.124-6.977 6.978 0 3.853 3.124 6.978 6.977 6.978 3.854 0 6.979-3.125 6.979-6.978 0-3.854-3.125-6.978-6.979-6.978zm-5.113 6.978c0-1.092.572-3.25.93-2.929l7.113 7.113c.488.525-1.837.931-2.93.931-2.825-.001-5.113-2.291-5.113-5.115zm9.298 2.929l-7.114-7.113c-.445-.483 1.837-.931 2.929-.931 2.827 0 5.115 2.289 5.115 5.114 0 1.093-.364 3.543-.93 2.93z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-ltr.svg
new file mode 100644
index 00000000..f31ec095
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M7 13.1l8.9 8.9c.8-.8.8-2 0-2.8l-6.1-6.1 6-6.1c.8-.8.8-2 0-2.8l-8.8 8.9z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-rtl.svg
new file mode 100644
index 00000000..02b4e387
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caret-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M16.5 13.1l-8.9 8.9c-.8-.8-.8-2 0-2.8l6.1-6.1-6-6.1c-.8-.8-.8-2 0-2.8l8.8 8.9z" id="path108"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretDown.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretDown.svg
new file mode 100644
index 00000000..a04ca572
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretDown.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 16l8.9-8.9c-.8-.8-2-.8-2.8 0l-6.1 6.1-6.1-6c-.8-.8-2-.8-2.8 0l8.9 8.8z" id="path4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretUp.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretUp.svg
new file mode 100644
index 00000000..d0e0c283
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/caretUp.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 6.5l8.9 8.9c-.8.8-2 .8-2.8 0l-6.1-6.1-6.1 6c-.8.8-2 .8-2.8 0l8.9-8.8z" id="path4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/case-sensitive.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/case-sensitive.svg
new file mode 100644
index 00000000..824790c5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/case-sensitive.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="regular-expression">
+ <path id="upper-case" d="M 7.53125,7 4,17 l 2.0625,0 0.71875,-2.40625 3.625,0 L 11.125,17 13.1875,17 9.65625,7 7.53125,7 z M 8.59375,8.53125 9.9375,13 7.25,13 8.59375,8.53125 z" />
+ <path id="lower-case" d="m 18.548697,17 -0.183254,-1.035072 -0.05451,0 c -0.349771,0.440361 -0.710892,0.746796 -1.083366,0.919307 -0.367941,0.167972 -0.849436,0.251959 -1.444489,0.251959 -0.564328,0 -0.954665,-0.20883 -1.377109,-0.626492 -0.417903,-0.417659 -0.626854,-1.012371 -0.626853,-1.784137 -1e-6,-0.80808 0.281628,-1.402791 0.844889,-1.784137 0.567801,-0.385878 1.193222,-0.607062 2.208372,-0.640111 l 1.321843,-0.04086 0,-0.333674 c 0,-0.771759 -0.395195,-1.15764 -1.185571,-1.157647 -0.608688,7e-6 -1.324118,0.183867 -2.146293,0.551584 L 14.134181,9.9184512 c 0.876685,-0.4585114 1.848761,-0.6877705 2.916233,-0.6877783 1.022038,7.8e-6 1.586855,0.2224573 2.131951,0.6673492 C 19.727448,10.342928 20,11.019356 20,11.927309 l 0,5.073215 -1.451303,0 m -0.394476,-3.527417 -0.804008,0.02724 c -0.604145,0.01816 -1.053844,0.127119 -1.349098,0.326866 -0.29526,0.199753 -0.442889,0.503919 -0.442886,0.912498 -3e-6,0.585634 0.336136,0.878451 1.008417,0.878449 0.481492,2e-6 0.865326,-0.138462 1.151503,-0.415391 0.29071,-0.276925 0.436067,-0.644648 0.436072,-1.103169 l 0,-0.626491" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/check.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/check.svg
new file mode 100644
index 00000000..cf7858ba
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/check.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="check">
+ <path d="M17 7.5L9.5 15 6 11.5 4.5 13l5 5L20 7.5c-.706-.706-2.294-.706-3 0z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/circle.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/circle.svg
new file mode 100644
index 00000000..436259e5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/circle.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><circle cx="12" cy="12" r="6"></circle></svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-ltr.svg
new file mode 100644
index 00000000..28ba0cb5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M7 12h9v-1h-9v1zm0 2h9v-1h-9v1zm0 2h9v-1h-9v1zm4-9h-4v1h4v-1zm0 2h-4v1h4v-1zm0-4h-4v1h4v-1zm5-2h2v16h-10c-1.7 0-3-1.3-3-3v-13h8v7l1.5-2 1.5 2v-7z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-rtl.svg
new file mode 100644
index 00000000..7625307c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/citeArticle-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g128">
+ <path d="M16 12h-9v-1h9v1zm0 2h-9v-1h9v1zm0 2h-9v-1h9v1zm-4-9h4v1h-4v-1zm0 2h4v1h-4v-1zm0-4h4v1h-4v-1zm-5-2h-2v16h10c1.7 0 3-1.3 3-3v-13h-8v7l-1.5-2-1.5 2v-7z" id="path130"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clear.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clear.svg
new file mode 100644
index 00000000..55a26c97
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clear.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="clear">
+ <path id="circle-with-cross" d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm4 11l-1 1-3-3-3 3-1-1 3-3-3-3 1-1 3 3 3-3 1 1-3 3 3 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clock.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clock.svg
new file mode 100644
index 00000000..1cf72670
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/clock.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm3 12l-4-3v-6h2v5l1.7 1.2c1.3.9 1 1.9.3 2.8z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-ltr.svg
new file mode 100644
index 00000000..4f0f64ec
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18.4 8.1c.8-.8.8-2 0-2.8l-6.4 6.5-5.6-5.6-1.4 1.4 5.6 5.6-5 5c-.8.8-.8 2 0 2.8l6.4-6.4 5.6 5.6 1.4-1.4-5.6-5.6 5-5.1z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-rtl.svg
new file mode 100644
index 00000000..d9829d0e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/close-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M5.6 8.1c-.8-.8-.8-2 0-2.8l6.4 6.5 5.6-5.6 1.4 1.4-5.6 5.6 5 5c.8.8.8 2 0 2.8l-6.4-6.4-5.6 5.6-1.4-1.4 5.6-5.6-5-5.1z" id="path140"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/code.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/code.svg
new file mode 100644
index 00000000..32f140d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/code.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
+ <g id="code">
+ <path id="left-bracket" d="M4 12v-1h1c1 0 1 0 1-1v-2.386c0-.514.024-.896.073-1.142.054-.252.139-.463.257-.633.204-.279.473-.475.808-.584.335-.115.872-.255 1.835-.255h1.027v1h-.752c-.457 0-.77.191-.936.408-.167.215-.312.445-.312 1.068v1.857c0 .729-.041 1.18-.244 1.493-.2.307-.562.529-1.09.667.535.155.9.385 1.096.688.199.303.238.757.238 1.484v1.862c0 .619.145.848.312 1.062.166.22.479.407.936.407l.752.004v1h-1.027c-.963 0-1.5-.133-1.835-.248-.335-.109-.604-.307-.808-.591-.118-.165-.203-.374-.257-.625-.049-.253-.073-.636-.073-1.149v-2.387c0-1 0-1-1-1h-1z"/>
+ <use transform="matrix(-1 0 0 1 24 0)" id="right-bracket" width="24" height="24" xlink:href="#left-bracket"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/collapse.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/collapse.svg
new file mode 100644
index 00000000..55aa8f8f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/collapse.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="collapse">
+ <path id="arrow" d="M6.697 15.714l5.303-5.302 5.303 5.302 1.414-1.414-6.717-6.717-6.717 6.717z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/comment.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/comment.svg
new file mode 100644
index 00000000..0ae7e63f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/comment.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="comment">
+ <path id="speech-bubble" d="M15 6h-6c-1.657 0-3 1.344-3 3v4c0 1.656 1.343 3 3 3v3l3-3h3c1.657 0 3-1.344 3-3v-4c0-1.656-1.343-3-3-3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-ltr.svg
new file mode 100644
index 00000000..eb4c360d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M16 5h-12v12c0 1.6 1.3 3 3 3h12v-12c0-1.7-1.4-3-3-3zm-8.5 12c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm0-6c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm4 3c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5zm0-6c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-rtl.svg
new file mode 100644
index 00000000..e929fdb1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/die-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M7 5h12v12c0 1.6-1.3 3-3 3h-12v-12c0-1.7 1.4-3 3-3zm8.5 12c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm0-6c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm-4 3c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5zm0-6c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5z" id="path150"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/downTriangle.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/downTriangle.svg
new file mode 100644
index 00000000..7bc1c228
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/downTriangle.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 18l8-10h-16z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-ltr.svg
new file mode 100644
index 00000000..d0d5bb5b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M16 11h-3v-7c-1.7 0-3 1.3-3 3v4h-3l4.5 5 4.5-5zm1 2v5h-10c-.6 0-1-.4-1-1v-4h-2v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-rtl.svg
new file mode 100644
index 00000000..9abb2ae4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/download-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g160">
+ <path d="M7 11h3v-7c1.7 0 3 1.3 3 3v4h3l-4.5 5-4.5-5zm-1 2v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3h-12v-7h2z" id="path162"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-ltr.svg
new file mode 100644
index 00000000..3972e070
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_3">
+ <path d="M17 2l-12 12-1 5 5-1 12-12c0-2-2-4-4-4zm-9.8 13.5c-.3-.3-.7-.6-1-.8 2.3-2.3 11.3-11.4 11.3-11.4.4.1.7.3 1 .7l-11.3 11.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-rtl.svg
new file mode 100644
index 00000000..978b2fd1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/edit-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_3">
+ <path d="M8 2l12 12 1 5-5-1-12-12c0-2 2-4 4-4zm9.8 13.5c.3-.3.7-.6 1-.8-2.3-2.3-11.3-11.4-11.3-11.4-.4.1-.7.3-1 .7l11.3 11.5z" id="path173"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-ltr.svg
new file mode 100644
index 00000000..7e376824
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-ltr.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_2">
+ <g id="g184">
+ <path d="M21 4v-1s0-3-3-3-3 3-3 3v1h-1v6h8v-6zm-1.5 0h-3v-1s0-1.5 1.5-1.5c1.48.06 1.5 1.5 1.5 1.5zm-6.5 5.6l-6.8 6.9c-.3-.3-.7-.6-1-.8 1.4-1.4 5-5 7.8-7.9v-1.8l-9 9-1 5 5-1 8-8h-3z" id="path186"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-rtl.svg
new file mode 100644
index 00000000..0b4751d2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editLock-rtl.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_2">
+ <g id="g184">
+ <path d="M4 4v-1s0-3 3-3 3 3 3 3v1h1v6h-8v-6zm1.5 0h3v-1s0-1.5-1.5-1.5c-1.48.06-1.5 1.5-1.5 1.5zm6.5 5.6l6.8 6.9c.3-.3.7-.6 1-.8-1.4-1.4-5-5-7.8-7.9v-1.8l9 9 1 5-5-1-8-8h3z" id="path186"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-ltr.svg
new file mode 100644
index 00000000..f346874e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-ltr.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g196">
+ <g id="g198">
+ <path d="M14.9 2.8c.9 0 1.8.2 2.7.6.9.4 1.6.9 1.9 1.6-2.8.1-5 1.1-6.6 3.1l1.3 2-6.7-.3.5-6.8 1.7 2c1.8-1.5 3.5-2.2 5.2-2.2z" id="path200"/>
+ </g>
+ </g>
+ <g id="g204">
+ <path d="M15.2 11.1l-2.6-.1-5.4 5.5c-.3-.3-.7-.6-1-.8.9-.9 2.8-2.8 4.7-4.8h-1.8l-4.1 4.1-1 5 5-1 7.8-7.8-1.6-.1zm5.4-5.1c-1.7 0-3.2.5-4.4 1.4l-.9.9.8 1.3.9 1.4 4-4c0-.3-.1-.7-.2-1h-.2z" id="path206"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-rtl.svg
new file mode 100644
index 00000000..5b59d452
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/editUndo-rtl.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g196">
+ <g id="g198">
+ <path d="M10.1 2.8c-.9 0-1.8.2-2.7.6-.9.4-1.6.9-1.9 1.6 2.8.1 5 1.1 6.6 3.1l-1.3 2 6.7-.3-.5-6.8-1.7 2c-1.8-1.5-3.5-2.2-5.2-2.2z" id="path200"/>
+ </g>
+ </g>
+ <g id="g204">
+ <path d="M9.8 11.1l2.6-.1 5.4 5.5c.3-.3.7-.6 1-.8-.9-.9-2.8-2.8-4.7-4.8h1.8l4.1 4.1 1 5-5-1-7.8-7.8 1.6-.1zm-5.4-5.1c1.7 0 3.2.5 4.4 1.4l.9.9-.8 1.3-.9 1.4-4-4c0-.3.1-.7.2-1h.2z" id="path206"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ellipsis.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ellipsis.svg
new file mode 100644
index 00000000..dd36a30d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ellipsis.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <g>
+ <path d="M8 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+ </g>
+ <g>
+ <path d="M14 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+ </g>
+ <g>
+ <path d="M20 13c0 .6-.2 1-.6 1.4-.4.4-.9.6-1.4.6-.6 0-1-.2-1.4-.6-.4-.4-.6-.9-.6-1.4s.2-1 .6-1.4c.4-.4.9-.6 1.4-.6s1 .2 1.4.6c.4.4.6.9.6 1.4z"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/expand.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/expand.svg
new file mode 100644
index 00000000..7666b41d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/expand.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="expand">
+ <path id="arrow" d="M17.303 8.283l-5.303 5.303-5.303-5.303-1.414 1.414 6.717 6.717 6.717-6.717z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-ltr.svg
new file mode 100644
index 00000000..827bc1b1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="external">
+ <path id="box" d="M2 2h3v1h-2v6h6v-2h1v3h-8z"/>
+ <path id="arrow" d="M6.211 2h3.789v3.789l-1.421-1.421-2.132 2.132-.947-.947 2.132-2.132z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-rtl.svg
new file mode 100644
index 00000000..c375ca0f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/external-link-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="external">
+ <path id="box" d="M7 3h2v6h-6v-2h-1v3h8v-8h-3z"/>
+ <path id="arrow" d="M2 5.789l1.421-1.421 2.132 2.132.947-.947-2.132-2.132 1.421-1.421h-3.789z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eye.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eye.svg
new file mode 100644
index 00000000..fa3bc3c1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eye.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M12 8c-5 0-11 6-11 6s6 6 11 6 11-6 11-6-6-6-11-6zm0 10c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z" id="path6"/>
+ <circle cx="12" cy="14" r="2" id="circle8"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eyeClosed.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eyeClosed.svg
new file mode 100644
index 00000000..fa1167df
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/eyeClosed.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M19.4 12.7c.7-.8 1.2-1.7 1.4-2.7h-1.6c-.9 2.5-3.9 4.4-7.7 4.6h-.1c-3.7-.2-6.8-2.1-7.7-4.6h-1.5c.2 1 .8 1.9 1.4 2.7l-2 2 .7.7 2-2c.8.6 1.7 1.2 2.7 1.7l-1 2.8.9.3 1-2.8c1 .3 2 .6 3.1.6v3h1v-3c1.1-.1 2.2-.3 3.1-.6l1 2.8.9-.3-1-2.8c1-.4 1.9-1 2.6-1.7l2 2 .7-.7-1.9-2z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-ltr.svg
new file mode 100644
index 00000000..f8578cf8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="find">
+ <path id="magnifying-glass" d="m 13.65625,11 c -1.921,0 -3.5,1.54775 -3.5,3.46875 0,1.92 1.579,3.5 3.5,3.5 0.749,0 1.432,-0.25225 2,-0.65625 l 0.09375,0.15625 2.375,2.375 c 0.19,0.189 0.53425,0.15325 0.78125,-0.09375 0.247,-0.247 0.314,-0.59125 0.125,-0.78125 l -2.375,-2.375 L 16.46875,16.5 C 16.87175,15.934 17.125,15.21775 17.125,14.46875 17.124,12.54875 15.57525,11 13.65625,11 z m 0,1.65625 c 1.011306,0 1.8125,0.801194 1.8125,1.8125 0,1.011306 -0.801194,1.84375 -1.8125,1.84375 -1.011306,0 -1.84375,-0.832444 -1.84375,-1.84375 0,-1.011306 0.832444,-1.8125 1.84375,-1.8125 z" />
+ <path id="text" d="M 6,5 6,7 16,7 16,5 6,5 z m 0,3 0,2 11,0 0,-2 -11,0 z m 0,3 0,2 3.53125,0 c 0.2825289,-0.797203 0.786096,-1.486208 1.4375,-2 L 6,11 z m 0,3 0,2 3.53125,0 C 9.3537004,15.520243 9.25,15.010236 9.25,14.46875 9.25,14.309811 9.2962033,14.154621 9.3125,14 L 6,14 z" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-rtl.svg
new file mode 100644
index 00000000..2a1e9c6f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/find-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="find">
+ <path id="magnifying-glass" d="m 11.343828,11.000025 c 1.921,0 3.5,1.54775 3.5,3.46875 0,1.92 -1.579,3.5 -3.5,3.5 -0.749,0 -1.432,-0.25225 -2,-0.65625 l -0.09375,0.15625 -2.375,2.375 c -0.19,0.189 -0.53425,0.15325 -0.78125,-0.09375 -0.247,-0.247 -0.314,-0.59125 -0.125,-0.78125 l 2.375,-2.375 0.1875,-0.09375 c -0.403,-0.566 -0.65625,-1.28225 -0.65625,-2.03125 10e-4,-1.92 1.54975,-3.46875 3.46875,-3.46875 z m 0,1.65625 c -1.011306,0 -1.8125,0.801194 -1.8125,1.8125 0,1.011306 0.801194,1.84375 1.8125,1.84375 1.011306,0 1.84375,-0.832444 1.84375,-1.84375 0,-1.011306 -0.832444,-1.8125 -1.84375,-1.8125 z" />
+ <path id="text" d="M 19,5 19,7 9,7 9,5 z m 0,3 0,2 -11,0 0,-2 z m 0,3 0,2 -3.53125,0 c -0.282529,-0.797203 -0.786096,-1.486208 -1.4375,-2 z m 0,3 0,2 -3.53125,0 C 15.6463,15.520243 15.75,15.010236 15.75,14.46875 15.75,14.309811 15.703797,14.154621 15.6875,14 z" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-ltr.svg
new file mode 100644
index 00000000..6e81d2bc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M14 6.5v-1.5c-1.4-1.5-5.2-1.2-6 0v-1h-1v15h1v-7c.8-.8 3.4-.9 5-.5v1.5c1.2 1.5 4.3 1.2 5 0v-7c-.7.7-2.7.9-4 .5z" id="path216"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-rtl.svg
new file mode 100644
index 00000000..4b743aac
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flag-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M11 6.5v-1.5c1.4-1.5 5.2-1.2 6 0v-1h1v15h-1v-7c-.8-.8-3.4-.9-5-.5v1.5c-1.2 1.5-4.3 1.2-5 0v-7c.7.7 2.7.9 4 .5z" id="path216"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-ltr.svg
new file mode 100644
index 00000000..49cdb7a2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-ltr.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g2990">
+ <g id="Layer_1">
+ <path id="path227" d="M14 6.5v-1.5c-1.4-1.5-5.2-1.2-6 0v-1h-1v15h1v-7c.8-.8 3.4-.9 5-.5v1.5c1.2 1.5 4.3 1.2 5 0v-7c-.7.7-2.7.9-4 .5z"/>
+ </g>
+ <g id="Layer_2">
+ <g id="g230">
+ <path id="path232" d="M17.997 1.989l.99.99-15.98 15.98-.99-.99z"/>
+ </g>
+ <g id="g234">
+ <path id="path236" d="M16.999 1.016l.99.99-15.98 15.98-.99-.99z" fill="#fff"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-rtl.svg
new file mode 100644
index 00000000..e470de42
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/flagUndo-rtl.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g2990">
+ <g id="Layer_1">
+ <path id="path227" d="M11 6.5v-1.5c1.4-1.5 5.2-1.2 6 0v-1h1v15h-1v-7c-.8-.8-3.4-.9-5-.5v1.5c-1.2 1.5-4.3 1.2-5 0v-7c.7.7 2.7.9 4 .5z"/>
+ </g>
+ <g id="Layer_2">
+ <g id="g230">
+ <path id="path232" d="M7.003 1.989l-.99.99 15.98 15.98.99-.99z"/>
+ </g>
+ <g id="g234">
+ <path id="path236" d="M8.001 1.016l-.99.99 15.98 15.98.99-.99z" fill="#fff"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-ltr.svg
new file mode 100644
index 00000000..63e0b1aa
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M2 5v15h20v-15h-20zm15 11h-9c-.6 0-1-.4-1-1v-6h3l2 1h5v6z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-rtl.svg
new file mode 100644
index 00000000..25bec742
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/folderPlaceholder-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M22 5v15h-20v-15h20zm-15 11h9c.6 0 1-.4 1-1v-6h-3l-2 1h-5v6z" id="path246"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-ltr.svg
new file mode 100644
index 00000000..191584eb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M11 13l-6-7h15l-6 7v7c-1.7 0-3-1.3-3-3v-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-rtl.svg
new file mode 100644
index 00000000..45f2f642
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/funnel-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g256">
+ <path d="M14 13l6-7h-15l6 7v7c1.7 0 3-1.3 3-3v-4z" id="path258"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/heart.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/heart.svg
new file mode 100644
index 00000000..6433201a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/heart.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M15 7c-2 0-3 2-3 2s-1-2-3-2c-2.5 0-4 2-4 4 0 4 5 5 7 8 2-3 7-4 7-8 0-2-1.5-4-4-4z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-ltr.svg
new file mode 100644
index 00000000..bb2545c5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-ltr.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="help">
+ <path id="circle" d="M12.001 2.085c-5.478 0-9.916 4.438-9.916 9.916 0 5.476 4.438 9.914 9.916 9.914 5.476 0 9.914-4.438 9.914-9.914 0-5.478-4.438-9.916-9.914-9.916zm.001 18c-4.465 0-8.084-3.619-8.084-8.083 0-4.465 3.619-8.084 8.084-8.084 4.464 0 8.083 3.619 8.083 8.084 0 4.464-3.619 8.083-8.083 8.083z"/>
+ <g id="question-mark">
+ <path id="top" d="M11.766 6.688c-2.5 0-3.219 2.188-3.219 2.188l1.411.854s.298-.791.901-1.229c.516-.375 1.625-.625 2.219.125.701.885-.17 1.587-1.078 2.719-.953 1.186-1 3.655-1 3.655h1.969s.135-2.318 1.041-3.381c.603-.707 1.443-1.338 1.443-2.494s-1.187-2.437-3.687-2.437z"/>
+ <path id="bottom" d="M11 16h2v2h-2z"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-rtl.svg
new file mode 100644
index 00000000..99c7f842
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/help-rtl.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="help">
+ <path id="circle" d="M11.999 2.085c5.478 0 9.916 4.438 9.916 9.916 0 5.476-4.438 9.914-9.916 9.914-5.476 0-9.914-4.438-9.914-9.914 0-5.478 4.438-9.916 9.914-9.916zm-.001 18c4.465 0 8.084-3.619 8.084-8.083 0-4.465-3.619-8.084-8.084-8.084-4.464 0-8.083 3.619-8.083 8.084 0 4.464 3.619 8.083 8.083 8.083z"/>
+ <g id="question-mark">
+ <path id="top" d="M12.234 6.688c2.5 0 3.219 2.188 3.219 2.188l-1.411.854s-.298-.791-.901-1.229c-.516-.375-1.625-.625-2.219.125-.701.885.17 1.587 1.078 2.719.953 1.186 1 3.655 1 3.655h-1.969s-.135-2.318-1.041-3.381c-.603-.707-1.443-1.338-1.443-2.494 0-1.156 1.187-2.437 3.687-2.437z"/>
+ <path id="bottom" d="M13 16h-2v2h2z"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/history.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/history.svg
new file mode 100644
index 00000000..35f15afe
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/history.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="history">
+ <path id="clock-hands" d="M17.26 15.076s-2.385-1.935-4.005-3.062c.72-2.397 1.702-6.559 1.702-6.559s-4.35 5.363-4.877 6.699c-.463 1.168 1.459 2.209 2.346 1.678 1.9.551 4.834 1.244 4.834 1.244z"/>
+ <path id="arrow" d="M12.086 2.085c-5.478 0-9.916 4.438-9.916 9.916 0 1.783.476 3.454 1.301 4.898l-2.223 2.04h5.688v-5.219l-2.066 1.896c-.55-1.088-.866-2.312-.866-3.615 0-4.465 3.619-8.084 8.084-8.084 4.464 0 8.083 3.619 8.083 8.084 0 4.464-3.619 8.083-8.083 8.083-1.145 0-2.228-.247-3.213-.678l-.833 1.634c1.235.557 2.602.874 4.045.874 5.476 0 9.914-4.438 9.914-9.914-.001-5.477-4.439-9.915-9.915-9.915z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-ltr.svg
new file mode 100644
index 00000000..bebe0a9e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M20 18l-4-4-2 2-4-4-2 1-4 5h16zm2-13v15h-20v-15h20z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-rtl.svg
new file mode 100644
index 00000000..88e0e3c0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/image-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g278">
+ <path d="M4 18l4-4 2 2 4-4 2 1 4 5h-16zm-2-13v15h20v-15h-20z" id="path280"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-ltr.svg
new file mode 100644
index 00000000..300e4b15
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M17 12v-4h-4v-3h-13v15h20v-8h-3zm-15 6l4-5 2-1 4 4 2-2 4 4h-16z"/>
+ <g>
+ <path d="M24 5h-4v-4h-2v4h-4v2h4v4h2v-4h4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-rtl.svg
new file mode 100644
index 00000000..70e32486
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageAdd-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M7 12v-4h4v-3h13v15h-20v-8h3zm15 6l-4-5-2-1-4 4-2-2-4 4h16z" id="path290"/>
+ <g id="g292">
+ <path d="M0 5h4v-4h2v4h4v2h-4v4h-2v-4h-4z" id="path294"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-ltr.svg
new file mode 100644
index 00000000..8cec9f5a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M19.5 4h-3v-1s0-1.5 1.5-1.5c1.5.06 1.5 1.5 1.5 1.5zm1.5 0v-1s0-3-3-3-3 3-3 3v1h-1v6h8v-6zm-8 7v-6h-11v15h20v-9zm-9 7l4-5 2-1 4 4 2-2 4 4z" id="path304"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-rtl.svg
new file mode 100644
index 00000000..6bb78f71
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/imageLock-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M4.5 4h3v-1s0-1.5-1.5-1.5c-1.5.06-1.5 1.5-1.5 1.5zm-1.5 0v-1s0-3 3-3 3 3 3 3v1h1v6h-8v-6zm8 7v-6h11v15h-20v-9zm9 7l-4-5-2-1-4 4-2-2-4 4z" id="path304"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-ltr.svg
new file mode 100644
index 00000000..ada33959
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M10 8h9v2h-9v-2zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8h-18v-2h18v2zm0 14h-18v-2h18v2zm-18-12v8l5-4-5-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-rtl.svg
new file mode 100644
index 00000000..9afedbbd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/indent-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g314">
+ <path d="M14 8h-9v2h9v-2zm0 3h-9v2h9v-2zm0 3h-6v2h6v-2zm-11-8h18v-2h-18v2zm0 14h18v-2h-18v2zm18-12v8l-5-4 5-4z" id="path316"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/info.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/info.svg
new file mode 100644
index 00000000..4bdefd46
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/info.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="info">
+ <path id="circled-i" d="M11.499 17c-3.036 0-5.499-2.464-5.499-5.5 0-3.037 2.462-5.5 5.499-5.5 3.037 0 5.501 2.462 5.501 5.5 0 3.036-2.464 5.5-5.501 5.5zm.002-12c-3.591 0-6.501 2.91-6.501 6.5s2.91 6.5 6.501 6.5c3.588 0 6.499-2.911 6.499-6.5s-2.911-6.5-6.499-6.5zM12 10v4h1v1h-3v-1h1v-3h-1v-1zM11 8h1v1h-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/insert.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/insert.svg
new file mode 100644
index 00000000..0833f84f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/insert.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="insert">
+ <path d="M13 5h-2v6h-6v2h6v6h2v-6h6v-2h-6z" id="plus"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-a.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-a.svg
new file mode 100644
index 00000000..a0e66bff
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-a.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-a">
+ <path id="a" d="M14.667 6h-1.372l-7 12h1.705l2.333-4h4l.667 4h1.667l-2-12zm-3.75 7l2.527-4.333.723 4.333h-3.25z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-keheh-jeem.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-keheh-jeem.svg
new file mode 100644
index 00000000..d4bff1be
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-keheh-jeem.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-arab-keheh-jeem">
+ <path id="arab-keheh-jeem" d="M18.125 5.844c-1.695.555-3.297 1.162-4.594 1.938-.49.299-.774.712-.875 1.125-.064.263-.035.572.063.781.189.405.539.574.844.813l.094-.125.531.625c.14.164.343.513.469.938.137.463.08.725 0 1.125h-3.438c-.338 0-.592.007-.766-.02-.339-.053-.256-.208-.234-.34.332-.127.564-.173.938-.141.29-.494.593-.885.906-1.313-.98.037-1.878.015-2.688-.094-.346-.047-.698-.186-1.094-.156-.357.026-.768.239-1.031.719-.246.448-.434.839-.656 1.281l.75-.469c.23-.142.484-.227.719-.219.157.005.275.054.406.094-.231.205-.509.402-.719.563-.301.26-.702.688-.906 1-.403.615-.694 1.084-.875 1.781-.179.689.004 1.339.469 1.75.426.376.846.519 1.281.563.65.065 1.205.093 2-.188.657-.231 1.021-.553 1.5-.969-.883.11-1.817.089-2.531.031-.871-.07-1.268-.384-1.469-.594-.271-.283-.307-.64-.156-1.219.036-.141.097-.323.25-.531.168-.228.364-.435.594-.656.451-.436 1.011-.737 1.461-.938-.045.206-.107.443-.055.688.049.229.248.379.438.469.259.122.506.155.688.156 1.421.011 2.862 0 4.281 0 .247 0 .452-.163.594-.375.139-.208.249-.481.344-.844.131-.499.094-1.062-.094-1.625-.182-.543-.418-1.009-.719-1.406-.335-.443-.674-.829-1-1.219 1.257-.815 2.716-1.239 3.969-1.688.121-.452.224-.926.313-1.313zm-9.469 8.438c-.262.394-.584.691-.875 1 .375.286.748.556 1.094.813.335-.303.626-.674.875-.969-.39-.268-.771-.588-1.094-.844z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-meem.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-meem.svg
new file mode 100644
index 00000000..bfbc9bf5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-arab-meem.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-arab-meem">
+ <path id="arab-meem" d="M16 9.729l-.93 2.19h-4.663c-.479 0-.857.122-1.135.367l-.061.11c-.184 2.016-.502 3.558-.955 4.627-.272.641-.633 1.252-1.082 1.833-.177.226-.219.186-.126-.119l.142-.504.17-.669.234-.87.002-.009.202-1.045.258-1.411.353-1.906c.191-.312.424-.638.699-.98.276-.342.589-.706.94-1.09.129-.092.697-.18 1.705-.266 1.05-.086 1.638-.183 1.765-.293l.065-.128c.007-.11-.011-.241-.054-.394-.043-.153-.12-.327-.231-.522-.22-.428-.438-.641-.654-.641-.294 0-.915.269-1.864.806-.359.208-.376.125-.051-.247 1.558-1.71 2.708-2.566 3.45-2.566.383 0 .671.131.863.394.135.195.25.599.344 1.21l.203 1.2c.106.586.242.895.409.925"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-armn-sha.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-armn-sha.svg
new file mode 100644
index 00000000..63de0f6c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-armn-sha.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-armn-sha">
+ <path id="armn-sha" d="M11.564 7.678c-.268-.13-.578-.22-.93-.268-.35-.047-.75-.07-1.197-.07h-1.11L8.586 6h1.724c.558 0 1.042.032 1.45.095.416.063.794.173 1.136.33l4.483 2.033-.324 1.67-2.624-1.165c-.126-.058-.27-.103-.433-.134-.164-.038-.356-.057-.576-.057-.583 0-1.137.095-1.663.284-.524.19-1 .46-1.425.812-.42.35-.777.78-1.072 1.283-.294.504-.504 1.074-.63 1.71-.242 1.255-.152 2.21.268 2.868.426.652 1.19.978 2.294.978.55 0 1.045-.08 1.48-.237.437-.156.815-.377 1.136-.66.326-.29.59-.633.796-1.033.21-.4.362-.84.457-1.323l.11-.56h1.6l-.12.59c-.13.674-.356 1.288-.676 1.845-.32.55-.725 1.026-1.214 1.425-.488.394-1.053.7-1.694.922-.642.215-1.343.323-2.105.323-.767 0-1.434-.113-2-.34-.568-.225-1.025-.553-1.372-.984-.347-.436-.573-.97-.678-1.607-.105-.637-.078-1.364.08-2.184.125-.66.346-1.273.66-1.835.316-.567.697-1.066 1.144-1.496.445-.436.944-.794 1.496-1.072.55-.284 1.13-.475 1.733-.575l-.466-.23"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-c.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-c.svg
new file mode 100644
index 00000000..b468deac
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-c.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-c">
+ <path id="c" d="M15.008 13.718l1.481.214c-.468 1.34-1.15 2.354-2.046 3.04-.896.686-1.901 1.029-3.015 1.029-1.359 0-2.438-.43-3.237-1.29-.794-.86-1.191-2.092-1.191-3.697 0-2.09.606-3.818 1.817-5.185 1.079-1.219 2.42-1.828 4.023-1.828 1.186 0 2.145.33 2.878.989.738.66 1.165 1.546 1.282 2.66l-1.397.135c-.148-.839-.453-1.464-.916-1.876-.458-.417-1.051-.625-1.779-.625-1.369 0-2.476.631-3.321 1.892-.733 1.087-1.099 2.377-1.099 3.871 0 1.193.282 2.103.847 2.731.565.628 1.3.942 2.206.942.774 0 1.473-.261 2.099-.784.626-.522 1.081-1.261 1.366-2.216"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-d.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-d.svg
new file mode 100644
index 00000000..92a834d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-d.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-d">
+ <path id="d" d="M7 18l2.462-12h3.557c.853 0 1.505.063 1.955.188.644.169 1.194.472 1.65.909.456.431.799.971 1.03 1.621.231.649.346 1.378.346 2.186 0 .966-.145 1.847-.435 2.644-.284.791-.66 1.49-1.127 2.095-.461.6-.947 1.072-1.456 1.416-.504.338-1.102.589-1.794.753-.526.126-1.172.188-1.939.188h-4.249m1.859-1.359h1.867c.842 0 1.591-.079 2.245-.237.408-.098.756-.243 1.046-.434.381-.246.727-.57 1.038-.974.408-.535.732-1.143.974-1.825.247-.688.37-1.468.37-2.341 0-.971-.166-1.716-.499-2.235-.333-.524-.756-.87-1.271-1.04-.381-.126-.974-.188-1.778-.188h-1.85l-1.907 9.274"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-e.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-e.svg
new file mode 100644
index 00000000..66a5ef5d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-e.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-e">
+ <path id="e" d="M7 18l2.474-12h8.526l-.282 1.367h-6.947l-.75 3.633h6.09l-.282 1.367h-6.09l-.877 4.274h7.438l-.282 1.359h-9.018"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-geor-kan.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-geor-kan.svg
new file mode 100644
index 00000000..3398904d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-geor-kan.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-geor-kan">
+ <path id="geor-kan" d="M15.057 14.663c-.441 2.225-1.834 3.337-4.178 3.337-1.919 0-2.879-.787-2.879-2.36 0-.298.036-.624.108-.977.083-.431.245-.836.488-1.217l1.241.605-.207.613c-.055.259-.083.497-.083.712 0 .972.521 1.458 1.564 1.458 1.307 0 2.101-.723 2.383-2.17l.058-.331c.044-.221.066-.425.066-.613 0-.928-.546-1.391-1.638-1.391h-1.117l.248-1.259h1.117c1.202-.005 1.908-.552 2.118-1.64.039-.182.058-.356.058-.522 0-1.143-.899-1.714-2.697-1.714l.232-1.193c2.708 0 4.062.875 4.062 2.625 0 .248-.028.516-.083.803-.204 1.093-1.051 1.825-2.54 2.195l-.033.166c1.23.199 1.845.823 1.845 1.872 0 .21-.025.433-.074.671l-.058.331"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-i.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-i.svg
new file mode 100644
index 00000000..93bec5a6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-i.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-i">
+ <path id="i" d="M12.5 17.999l.249-.994h-1.5l2.509-10.037h1.5l.242-.967h-5l-.242.967h1.5l-2.509 10.037h-1.5l-.249.994z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-k.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-k.svg
new file mode 100644
index 00000000..d4831549
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-k.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-k">
+ <path id="k" d="M12.018 10.652l4.982-4.652h-2l-5.309 5.234 1.309-5.234h-1.5l-3 12h1.5l1.173-4.693 1.54-1.438c.287 4.131 3.287 6.131 3.287 6.131h2s-4-2-3.982-7.348z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-s.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-s.svg
new file mode 100644
index 00000000..4f6364cb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/italic-s.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="italic-s">
+ <path id="s" d="M16.474 6.589l-.302 1.526c-.522-.279-1.041-.488-1.557-.628-.511-.145-1.007-.217-1.487-.217-.935 0-1.679.204-2.231.612-.553.408-.829.95-.829 1.627 0 .372.101.658.302.86.207.196.733.408 1.58.635l.937.232c1.059.274 1.795.622 2.208 1.046.413.418.62 1.007.62 1.766 0 1.167-.46 2.117-1.379 2.851-.914.733-2.12 1.1-3.618 1.1-.615 0-1.232-.062-1.852-.186-.62-.119-1.242-.302-1.867-.55l.318-1.611c.573.356 1.147.625 1.72.806.578.181 1.154.271 1.728.271.976 0 1.759-.217 2.347-.651.589-.434.883-.999.883-1.697 0-.465-.119-.816-.356-1.054-.232-.243-.736-.462-1.511-.658l-.937-.24c-1.069-.279-1.8-.599-2.192-.961-.387-.367-.581-.878-.581-1.534 0-1.152.442-2.094 1.325-2.828.888-.739 2.043-1.108 3.463-1.108.553 0 1.1.049 1.642.147.542.098 1.085.245 1.627.442"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-ltr.svg
new file mode 100644
index 00000000..c7e16033
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M16 9v-1h-6v1h6zm-2 2v-1h-4v1h4zm-8-6h1v16h-1v-16zm2 0h10v13c0 1.7-1.3 3-3 3h-7v-16z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-rtl.svg
new file mode 100644
index 00000000..2d16be37
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/journal-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M8 9v-1h6v1h-6zm2 2v-1h4v1h-4zm8-6h-1v16h1v-16zm-2 0h-10v13c0 1.7 1.3 3 3 3h7v-16z" id="path326"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-ltr.svg
new file mode 100644
index 00000000..8dfb89ae
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M14.5 4c-3 0-5.5 2.5-5.5 5.5 0 1 .3 1.9.7 2.8l-5.7 5.7v2h4v-2h2v-2h2l1.2-1.2c.4.1.9.2 1.3.2 3 0 5.5-2.5 5.5-5.5s-2.5-5.5-5.5-5.5zm1.5 5c-.8 0-1.5-.7-1.5-1.5s.7-1.5 1.5-1.5 1.5.7 1.5 1.5-.7 1.5-1.5 1.5z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-rtl.svg
new file mode 100644
index 00000000..06392874
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/key-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M9.5 4c3 0 5.5 2.5 5.5 5.5 0 1-.3 1.9-.7 2.8l5.7 5.7v2h-4v-2h-2v-2h-2l-1.2-1.2c-.4.1-.9.2-1.3.2-3 0-5.5-2.5-5.5-5.5s2.5-5.5 5.5-5.5zm-1.5 5c.8 0 1.5-.7 1.5-1.5s-.7-1.5-1.5-1.5-1.5.7-1.5 1.5.7 1.5 1.5 1.5z" id="path336"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-ltr.svg
new file mode 100644
index 00000000..ea5055c8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M3 7v9c0 1.7 1.3 3 3 3h15v-12h-18zm8 2h2v2h-2v-2zm0 3h2v2h-2v-2zm-3-3h2v2h-2v-2zm0 3h2v2h-2v-2zm-1 5h-1c-.6 0-1-.4-1-1v-1h2v2zm0-3h-2v-2h2v2zm0-3h-2v-2h2v2zm9 6h-8v-2h8v2zm0-3h-2v-2h2v2zm0-3h-2v-2h2v2zm3 6h-2v-2h2v2zm0-3h-2v-2h2v2zm0-3h-2v-2h2v2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-rtl.svg
new file mode 100644
index 00000000..b35d108d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/keyboard-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g346">
+ <path d="M21 7v9c0 1.7-1.3 3-3 3h-15v-12h18zm-8 2h-2v2h2v-2zm0 3h-2v2h2v-2zm3-3h-2v2h2v-2zm0 3h-2v2h2v-2zm1 5h1c.6 0 1-.4 1-1v-1h-2v2zm0-3h2v-2h-2v2zm0-3h2v-2h-2v2zm-9 6h8v-2h-8v2zm0-3h2v-2h-2v2zm0-3h2v-2h-2v2zm-3 6h2v-2h-2v2zm0-3h2v-2h-2v2zm0-3h2v-2h-2v2z" id="path348"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/language.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/language.svg
new file mode 100644
index 00000000..081e49a1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/language.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="language">
+ <path id="japanese" d="M17.533 9.81l.271-.59 1.041.407-.18.363c.661.271 1.101.468 1.312.589.331.211.618.514.86.905.211.393.316.846.316 1.358 0 .786-.302 1.479-.905 2.083-.604.634-1.66 1.057-3.169 1.268-.121-.361-.258-.679-.408-.95.965-.151 1.645-.333 2.037-.545.454-.21.785-.481.998-.813.21-.303.314-.663.314-1.087 0-.482-.136-.905-.407-1.269-.331-.331-.8-.589-1.402-.77-.333.634-.649 1.117-.951 1.449-.242.332-.694.906-1.358 1.721.09.393.181.709.272.951l-1.042.362-.091-.498c-.423.361-.801.617-1.133.77-.361.15-.664.226-.905.226-.303 0-.574-.136-.814-.407-.243-.301-.362-.68-.362-1.132 0-.604.136-1.147.407-1.63.241-.453.603-.89 1.086-1.313.272-.241.725-.528 1.359-.86 0-.271.03-.799.09-1.585-.514.03-.921.045-1.222.045-.393 0-.711-.015-.951-.045l-.046-1.041c.725.091 1.494.135 2.31.135 0-.149.075-.738.227-1.766l1.177.183c-.151.542-.256 1.041-.316 1.493.242-.029.543-.075.906-.136.362-.061.573-.091.634-.091s.648-.15 1.766-.453l.046 1.041c-.967.243-2.145.439-3.532.591-.062.663-.092 1.086-.092 1.266.663-.151 1.284-.225 1.857-.225zm-2.672 3.893c-.061-.481-.136-1.252-.227-2.31-.573.424-1.041.86-1.403 1.313-.303.423-.452.875-.452 1.358 0 .241.044.438.136.588.09.092.195.137.316.137.363.001.907-.361 1.63-1.086zm.771-2.763c0 .483.029 1.088.09 1.811.604-.905 1.057-1.599 1.359-2.082-.574.06-1.058.151-1.449.271z"/>
+ <path id="english" d="M9.497 15.981h1.851l-3.084-8.949h-1.85l-3.081 8.949h1.85l.557-1.981h3.209l.548 1.981zm-3.489-3.377l1.331-3.782 1.344 3.782h-2.675z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-ltr.svg
new file mode 100644
index 00000000..47e71b39
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="layout-ltr">
+ <path id="text" d="M5 19v-14h6v8h8v6h-14z"/>
+ <path id="float" d="M13 5v6h6v-6h-6zm5 5h-4v-4h4v4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-rtl.svg
new file mode 100644
index 00000000..fe9ee617
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/layout-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="layout-rtl">
+ <path id="text" d="M5 19v-6h8v-8h6v14h-14z"/>
+ <path id="float" d="M5 5v6h6v-6h-6zm1 1h4v4h-4v-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-ltr.svg
new file mode 100644
index 00000000..841ba7d4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-ltr.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-487 489 24 24" enable-background="new -487 489 24 24" xml:space="preserve">
+<g>
+ <path d="M-471.2,493.6c-2.1,0-3.6,1.9-5.1,3.3c0.2,0,0.5-0.1,0.8-0.1c0.5,0,1,0.1,1.5,0.3c0.8-0.8,1.6-1.7,2.8-1.7
+ c0.6,0,1.3,0.3,1.8,0.7c1,1,1,2.6,0,3.6l-2.6,2.6c-0.4,0.4-1.2,0.7-1.8,0.7c-1.4,0-2.1-0.9-2.6-2l-1.3,1.3c0.8,1.5,2,2.6,3.8,2.6
+ c1.2,0,2.3-0.5,3-1.3l2.6-2.6c0.9-0.9,1.5-2,1.5-3.3C-467,495.5-469,493.6-471.2,493.6z M-475.5,505.7l-0.9,0.9
+ c-0.4,0.4-1.2,0.7-1.8,0.7c-0.6,0-1.3-0.3-1.8-0.7c-1-1-1-2.7,0-3.6l2.6-2.6c0.4-0.4,1.2-0.7,1.8-0.7c1.4,0,2.1,1,2.6,2l1.3-1.3
+ c-0.8-1.5-2-2.6-3.8-2.6c-1.2,0-2.3,0.5-3,1.3l-2.6,2.6c-1.7,1.7-1.7,4.4,0,6c1.6,1.6,4.4,1.7,5.9,0l1.9-1.9
+ c-0.3,0.1-0.6,0.1-0.9,0.1C-474.7,505.9-475.1,505.9-475.5,505.7z"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-rtl.svg
new file mode 100644
index 00000000..d4c2fd66
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/link-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g358">
+ <path d="M9.025 3.6c2.1 0 3.6 1.9 5.1 3.3-.2 0-.5-.1-.8-.1-.5 0-1 .1-1.5.3-.8-.8-1.6-1.7-2.8-1.7-.6 0-1.3.3-1.8.7-1 1-1 2.6 0 3.6l2.6 2.6c.4.4 1.2.7 1.8.7 1.4 0 2.1-.9 2.6-2l1.3 1.3c-.8 1.5-2 2.6-3.8 2.6-1.2 0-2.3-.5-3-1.3l-2.6-2.6c-.9-.9-1.5-2-1.5-3.3.2-2.2 2.2-4.1 4.4-4.1zm4.3 12.1l.9.9c.4.4 1.2.7 1.8.7.6 0 1.3-.3 1.8-.7 1-1 1-2.7 0-3.6l-2.6-2.6c-.4-.4-1.2-.7-1.8-.7-1.4 0-2.1 1-2.6 2l-1.3-1.3c.8-1.5 2-2.6 3.8-2.6 1.2 0 2.3.5 3 1.3l2.6 2.6c1.7 1.7 1.7 4.4 0 6-1.6 1.6-4.4 1.7-5.9 0l-1.9-1.9c.3.1.6.1.9.1.5 0 .9 0 1.3-.2z" id="path360"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-ltr.svg
new file mode 100644
index 00000000..09a4ff5d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M21 7h-12v-2h12v2zm-14-1c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7h-12v-2h12v2zm-14-1c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm14 7h-12v-2h12v2zm-14-1c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-rtl.svg
new file mode 100644
index 00000000..67b9dfee
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listBullet-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M3 7h12v-2h-12v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zm-14 7h12v-2h-12v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2zm-14 7h12v-2h-12v2zm14-1c0 1.1.9 2 2 2s2-.9 2-2-.9-2-2-2-2 .9-2 2z" id="path370"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-ltr.svg
new file mode 100644
index 00000000..87e8854e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M21 7h-13v-2h13v2zm0 6h-13v-2h13v2zm0 6h-13v-2h13v2zm-17-15h2v4h-1v-3h-1zm-1 6v-1h3v3h-2v1h2v1h-3v-3h2v-1zm3 10h-3v-1h2v-1h-1v-1h1v-1h-2v-1h3z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-rtl.svg
new file mode 100644
index 00000000..831a5fb9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/listNumbered-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M3 7h13v-2h-13zm0 6h13v-2h-13zm0 6h13v-2h-13zm15-15h2v4h-1v-3h-1zm0 6v-1h3v3h-2v1h2v1h-3v-3h2v-1zm3 10h-3v-1h2v-1h-1v-1h1v-1h-2v-1h3z" id="path380"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-ltr.svg
new file mode 100644
index 00000000..59454922
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g390">
+ <path d="M15 8s0-3-2.5-3-2.5 3-2.5 3v1h5zm2 0v1h2v10h-10c-1.7 0-3-1.3-3-3v-7h2v-1s0-5 4.5-5 4.5 5 4.5 5z" id="path392"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-rtl.svg
new file mode 100644
index 00000000..0591f661
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/lock-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g390">
+ <path d="M10 8s0-3 2.5-3 2.5 3 2.5 3v1h-5zm-2 0v1h-2v10h10c1.7 0 3-1.3 3-3v-7h-2v-1s0-5-4.5-5-4.5 5-4.5 5z" id="path392"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-ltr.svg
new file mode 100644
index 00000000..4af765ca
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M15 14v3l5-4.5-5-4.5v3h-7c0 1.7 1.3 3 3 3h4zm-1-9h-10v15h10v-2h-8v-11h8v-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-rtl.svg
new file mode 100644
index 00000000..f72c04ad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logOut-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g402">
+ <path d="M9 14v3l-5-4.5 5-4.5v3h7c0 1.7-1.3 3-3 3h-4zm1-9h10v15h-10v-2h8v-11h-8v-2z" id="path404"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-cc.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-cc.svg
new file mode 100644
index 00000000..124b2101
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-cc.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M12 6c-3.9 0-7 3.1-7 7s3.1 7 7 7 7-3.1 7-7-3.1-7-7-7zm0 13c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6zm-1.7-4.6c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5zm4 0c-.7 0-1-.4-1-1.2s.3-1.2 1-1.2c.4 0 .6.2.8.6l.9-.5c-.4-.7-1-1-1.9-1-.6 0-1.1.2-1.5.6s-.6.8-.6 1.5.2 1.2.6 1.6c.4.4.9.6 1.5.6.8 0 1.4-.4 1.9-1.1l-.9-.4c-.2.3-.5.5-.8.5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikimediaCommons.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikimediaCommons.svg
new file mode 100644
index 00000000..079e1773
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikimediaCommons.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M15.4 7.8c-2-.9-2.3-2.5-2.4-2.8.1.1 2 1 2 1l-3-5-3 5 2-1s0 .8.6 2.1c.8 1.5 2.2 2.2 2.2 2.2s1.6.7 2.2 1.3l-.7.7-.5-.5-.4 1.8 1.8-.4-.5-.5.7-.7c.9 1 1.5 2.3 1.6 3.8h-1v-.8l-1.5 1 1.5 1v-.8h1c-.1 1.5-.6 2.8-1.6 3.8l-.7-.7.5-.5-1.8-.4.4 1.8.5-.5.7.7c-1 .9-2.3 1.5-3.8 1.6v-1h.8l-1-1.5-1 1.5h.8v1c-1.5-.1-2.8-.6-3.8-1.6l.7-.7.5.5.4-1.8-1.8.4.5.5-.7.7c-.9-1-1.5-2.3-1.6-3.8h1v.8l1.5-1-1.5-1v.8h-1c.1-1.5.6-2.8 1.6-3.8l.7.7-.5.5 1.8.4-.4-1.8-.5.5-.7-.7-1.5-1.4c-1.5 1.4-2.5 3.5-2.5 5.8 0 4.4 3.6 8 8 8s8-3.6 8-8c0-3.2-1.9-5.9-4.6-7.2z"/>
+ <circle cx="12" cy="15" r="3"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikipedia.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikipedia.svg
new file mode 100644
index 00000000..6672d9dc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/logo-wikipedia.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M22.3 6.3c0 .2 0 .3-.1.3-.7.1-1.2.5-1.6 1.1-.1.2-.2.4-.3.7l-4.6 10.1c-.1.2-.2.3-.2.3s-.1.1-.2.1c-.2 0-.4-.1-.5-.4l-2.6-5.5-2.8 5.5c-.1.3-.3.4-.5.4s-.4-.1-.5-.4l-4.3-10.1c-.3-.8-.6-1.2-.8-1.4-.2-.2-.5-.3-1-.4-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h4.3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.6.1-1 .2-1.1.4-.1.2 0 .6.3 1.2l3.6 8.2h.1l2.2-4.4-1.7-3.6c-.3-.7-.6-1.2-.8-1.4s-.5-.3-.9-.4c-.1-.1-.1-.2-.1-.3 0-.2 0-.3.1-.3h3.6c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.4.1-.6.2-.6.4s.1.6.4 1.2l1 1.9 1-1.9c.3-.6.5-.9.5-1.1 0-.2 0-.3-.1-.4-.1-.1-.3-.1-.5-.1l-.1-.3c0-.2 0-.3.1-.3h3c.1.1.1.2.1.3 0 .2 0 .3-.1.3-.5.1-.8.2-1.1.5-.3.3-.6.7-.8 1.3l-1.3 2.8 2.5 5.2h.1l3.7-8.1c.3-.5.3-.9.2-1.2-.1-.3-.5-.4-1.1-.5-.1-.1-.1-.2-.1-.3s0-.3.1-.3h3.7c-.2.1-.2.2-.2.3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-ltr.svg
new file mode 100644
index 00000000..0fc47737
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M15 6l-6-2-6 2v15l6-2 6 2 6-2v-15l-6 2zm-6.3 12.1l-4.7 1.5v-12.9l5-1.7v12.9l-.3.2zm11.3.2l-5 1.7v-12.9l.3-.1 4.7-1.6v12.9z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-rtl.svg
new file mode 100644
index 00000000..b33f1e39
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/map-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M9 6l6-2 6 2v15l-6-2-6 2-6-2v-15l6 2zm6.3 12.1l4.7 1.5v-12.9l-5-1.7v12.9l.3.2zm-11.3.2l5 1.7v-12.9l-.3-.1-4.7-1.6v12.9z" id="path424"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPin.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPin.svg
new file mode 100644
index 00000000..f422c84f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPin.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M19 12c0-3.9-3.1-7-7-7s-7 3.1-7 7c0 1.4.4 2.6 1.1 3.7l5.9 7.3 5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7zm-7 4c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z" id="path4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-ltr.svg
new file mode 100644
index 00000000..9a54eb6a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-ltr.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g434">
+ <g id="g436">
+ <path d="M24 4h-4v-4h-2v4h-4v2h4v4h2v-4h4z" id="path438"/>
+ </g>
+ </g>
+ <path d="M18 11h-1v-3.9l-.1-.1h-3.9v-1.9c-.3-.1-.7-.1-1-.1-3.9 0-7 3.1-7 7 0 1.4.4 2.6 1.1 3.7l5.9 7.3 5.9-7.3c.7-1.1 1.1-2.3 1.1-3.7 0-.3 0-.7-.1-1h-.9zm-6 5c-2.2 0-4-1.8-4-4s1.8-4 4-4 4 1.8 4 4-1.8 4-4 4z" id="path440"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-rtl.svg
new file mode 100644
index 00000000..d3e152e0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/mapPinAdd-rtl.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g434">
+ <g id="g436">
+ <path d="M0 4h4v-4h2v4h4v2h-4v4h-2v-4h-4z" id="path438"/>
+ </g>
+ </g>
+ <path d="M6 11h1v-3.9l.1-.1h3.9v-1.9c.3-.1.7-.1 1-.1 3.9 0 7 3.1 7 7 0 1.4-.4 2.6-1.1 3.7l-5.9 7.3-5.9-7.3c-.7-1.1-1.1-2.3-1.1-3.7 0-.3 0-.7.1-1h.9zm6 5c2.2 0 4-1.8 4-4s-1.8-4-4-4-4 1.8-4 4 1.8 4 4 4z" id="path440"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/menu.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/menu.svg
new file mode 100644
index 00000000..89fd9789
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/menu.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-487 489 24 24" enable-background="new -487 489 24 24" xml:space="preserve">
+<g id="menu">
+ <path id="lines" d="M-481,505h12c0.6,0,1,0.4,1,1v1c0,0.6-0.4,1-1,1h-12c-0.6,0-1-0.4-1-1v-1C-482,505.4-481.6,505-481,505z
+ M-482,501v1c0,0.6,0.4,1,1,1h12c0.6,0,1-0.4,1-1v-1c0-0.6-0.4-1-1-1h-12C-481.6,500-482,500.4-482,501z M-482,496v1
+ c0,0.6,0.4,1,1,1h12c0.6,0,1-0.4,1-1v-1c0-0.6-0.4-1-1-1h-12C-481.6,495-482,495.4-482,496z"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-ltr.svg
new file mode 100644
index 00000000..3f308ff7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M21 9c0-1.7-1.3-3-3-3h-15v3l9 4 9-4zm-18 2v6c0 1.7 1.3 3 3 3h15v-9l-9 4-9-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-rtl.svg
new file mode 100644
index 00000000..fa61aa18
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/message-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g450">
+ <path d="M3 9c0-1.7 1.3-3 3-3h15v3l-9 4-9-4zm18 2v6c0 1.7-1.3 3-3 3h-15v-9l9 4 9-4z" id="path452"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-ltr.svg
new file mode 100644
index 00000000..51e6611a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="move-ltr">
+ <path id="arrow" d="M8.935 7.181l5.302 5.302-5.302 5.303 1.414 1.414 6.716-6.717-6.716-6.716z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-rtl.svg
new file mode 100644
index 00000000..bcee09d9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="move-rtl">
+ <path id="arrow" d="M15.065 17.786l-5.302-5.303 5.302-5.302-1.414-1.414-6.716 6.716 6.716 6.717z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move.svg
new file mode 100644
index 00000000..9063bd48
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/move.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M20 11l-4-3v2h-3v-3h2l-3-4-3 4h2v3h-3v-2l-4 3 4 3v-2h3v3h-2l3 4 3-4h-2v-3h3v2z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-ltr.svg
new file mode 100644
index 00000000..b8ea833e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M12 5l2.5 2.5-3.5 3.5c-1.2 1.2-1.2 2.8 0 4l5.5-5.5 2.5 2.5v-7h-7zm5 12h-9c-.6 0-1-.4-1-1v-9h3l-2-2h-3v11c0 1.7 1.3 3 3 3h11v-3l-2-2v3z" id="path6"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-rtl.svg
new file mode 100644
index 00000000..58a9eeb2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newWindow-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g462">
+ <path d="M12 5l-2.5 2.5 3.5 3.5c1.2 1.2 1.2 2.8 0 4l-5.5-5.5-2.5 2.5v-7h7zm-5 12h9c.6 0 1-.4 1-1v-9h-3l2-2h3v11c0 1.7-1.3 3-3 3h-11v-3l2-2v3z" id="path464"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-ltr.svg
new file mode 100644
index 00000000..dad5f51c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="line_return">
+ <path d="M17.8 5.7c-.5 0-.9.2-1.2.5s-.5.7-.5 1.2v4.3h-5.1v-4l-6 5.5 6 5.5v-4h8v-9h-1.2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-rtl.svg
new file mode 100644
index 00000000..fd758cc6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newline-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="line_return">
+ <path d="M6.2 5.7c.5 0 .9.2 1.2.5.3.3.5.7.5 1.2v4.3H13v-4l6 5.5-6 5.5v-4H5v-9h1.2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-ltr.svg
new file mode 100644
index 00000000..46471a33
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M6 7v12c-.6 0-1-.4-1-1v-9h-1v9c0 1.1.9 2 2 2h15v-13h-15zm9 11h-7v-1h7v1zm0-2h-7v-1h7v1zm0-2h-7v-1h7v1zm4 4h-3v-5h3v5zm0-7h-11v-2h11v2z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-rtl.svg
new file mode 100644
index 00000000..7564dff0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/newspaper-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M19 7v12c.6 0 1-.4 1-1v-9h1v9c0 1.1-.9 2-2 2h-15v-13h15zm-9 11h7v-1h-7v1zm0-2h7v-1h-7v1zm0-2h7v-1h-7v1zm-4 4h3v-5h-3v5zm0-7h11v-2h-11v2z" id="path474"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-ltr.svg
new file mode 100644
index 00000000..601428e2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M16 14l2 2v-11h-4v2h2zm0 2l-7-7-2-2-1-1-1-1-3-3-1 1 2 2h-1v14h4v-2h-2v-10h1l2 2v10h4v-2h-2v-6l6 6h-1v2h3l4 4 1-1-4-4zm-5-9v-2h-4l2 2zm8-2v2h2v10h-2l2 2h2v-14z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-rtl.svg
new file mode 100644
index 00000000..31785a3c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/noWikiText-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g484">
+ <path d="M8 14l-2 2v-11h4v2h-2zm0 2l7-7 2-2 1-1 1-1 3-3 1 1-2 2h1v14h-4v-2h2v-10h-1l-2 2v10h-4v-2h2v-6l-6 6h1v2h-3l-4 4-1-1 4-4zm5-9v-2h4l-2 2zm-8-2v2h-2v10h2l-2 2h-2v-14z" id="path486"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-ltr.svg
new file mode 100644
index 00000000..4264ff08
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M10 8h9v2h-9v-2zm0 3h9v2h-9v-2zm0 3h6v2h-6v-2zm11-8h-18v-2h18v2zm0 14h-18v-2h18v2zm-18-8l5 4v-8l-5 4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-rtl.svg
new file mode 100644
index 00000000..2479343e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outdent-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g496">
+ <path d="M14 8h-9v2h9v-2zm0 3h-9v2h9v-2zm0 3h-6v2h6v-2zm-11-8h18v-2h-18v2zm0 14h18v-2h-18v2zm18-8l-5 4v-8l5 4z" id="path498"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-ltr.svg
new file mode 100644
index 00000000..9c0ea598
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="outline-ltr">
+ <path id="text" d="M5 13h14v6h-14v-6z"/>
+ <path id="float" d="M5 5v6h6v-6h-6zm5 5h-4v-4h4v4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-rtl.svg
new file mode 100644
index 00000000..2a3428e9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/outline-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="outline-rtl">
+ <path id="text" d="M19 19h-14v-6h14v6z"/>
+ <path id="float" d="M13 5v6h6v-6h-6zm1 1h4v4h-4v-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-ltr.svg
new file mode 100644
index 00000000..92fc07ed
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M2 3h18v2h-16v12h-2v-14zm13 13l-4-4-4 5h13l-3-3-2 2zm-10-10h17v13h-17v-13z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-rtl.svg
new file mode 100644
index 00000000..d989d3d4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/photoGallery-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g508">
+ <path d="M22 3h-18v2h16v12h2v-14zm-13 13l4-4 4 5h-13l3-3 2 2zm10-10h-17v13h17v-13z" id="path510"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/picture.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/picture.svg
new file mode 100644
index 00000000..7400bca9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/picture.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="picture">
+ <path id="frame" d="M18 4h-12c-2-.007-3 .993-3 2.993l.014 9.007c-.014 2 .986 2.988 2.986 3h12c2-.012 2.994-1 3-3.006v-9.001c-.006-2-1-3-3-2.993zm1 13h-14v-11h14v11z"/>
+ <path id="mountains" d="M6 13.5l3.5-3.5 2.328 2.312-1.312 1.094.875 1.032 4.109-3.438 2.5 2v3h-12z"/>
+ <path id="sky" d="M6 12l3.516-4.156 3.046 3.172 2.938-2.016 2.5 2v-4h-12z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-ltr.svg
new file mode 100644
index 00000000..5ce95636
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M12 5c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-2 12v-8l6 4-6 4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-rtl.svg
new file mode 100644
index 00000000..591a5d3a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/play-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g520">
+ <path d="M12 5c4.4 0 8 3.6 8 8s-3.6 8-8 8-8-3.6-8-8 3.6-8 8-8zm2 12v-8l-6 4 6 4z" id="path522"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-ltr.svg
new file mode 100644
index 00000000..baae35e9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18 8h-1v-4h-10v4h-4v6c0 1.7 1.3 3 3 3h1v3h10v-3h4v-6c0-1.7-1.3-3-3-3zm-10-3h8v3h-8v-3zm8 14h-8v-6h8v6z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-rtl.svg
new file mode 100644
index 00000000..8294afd5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/printer-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M6 8h1v-4h10v4h4v6c0 1.7-1.3 3-3 3h-1v3h-10v-3h-4v-6c0-1.7 1.3-3 3-3zm10-3h-8v3h8v-3zm-8 14h8v-6h-8v6z" id="path532"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-ltr.svg
new file mode 100644
index 00000000..97b77bb4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18 9.9c-.7 0-1.4.3-1.8.9v-4.8h-4c.2-.4.4-.8.4-1.2 0-1.2-1-2.2-2.2-2.2-1.3-.1-2.3.9-2.3 2.2 0 .4.2.8.4 1.2h-4.4v3.6l.6-.1c1.4 0 2.5 1.1 2.5 2.5s-1.1 2.5-2.5 2.5c-.2 0-.4 0-.6-.1v3.6h4.9c-.5.4-.9 1-.9 1.8 0 1.2 1 2.2 2.3 2.2 1.2 0 2.2-1 2.2-2.2 0-.7-.3-1.4-.9-1.8h4.5v-4.5c.4.5 1 .9 1.8.9 1.2 0 2.2-1 2.2-2.2 0-1.3-1-2.3-2.2-2.3z" id="path542"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-rtl.svg
new file mode 100644
index 00000000..0ad5f375
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/puzzle-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M6.3 9.9c.7 0 1.4.3 1.8.9v-4.8h4c-.2-.4-.4-.8-.4-1.2 0-1.2 1-2.2 2.2-2.2 1.3-.1 2.3.9 2.3 2.2 0 .4-.2.8-.4 1.2h4.4v3.6l-.6-.1c-1.4 0-2.5 1.1-2.5 2.5s1.1 2.5 2.5 2.5c.2 0 .4 0 .6-.1v3.6h-4.9c.5.4.9 1 .9 1.8 0 1.2-1 2.2-2.3 2.2-1.2 0-2.2-1-2.2-2.2 0-.7.3-1.4.9-1.8h-4.5v-4.5c-.4.5-1 .9-1.8.9-1.2 0-2.2-1-2.2-2.2 0-1.3 1-2.3 2.2-2.3z" id="path542"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-ltr.svg
new file mode 100644
index 00000000..b3b923e5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-ltr.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-487 489 24 24" enable-background="new -487 489 24 24" xml:space="preserve">
+<g>
+ <path d="M-468.9,498.1c0.2-0.1,0.5-0.2,0.6-0.5s0.2-0.5,0.2-0.9V496c-1,0.2-1.5,0.2-2.6,0.6c-0.7,0.4-1.2,0.9-1.7,1.4
+ c-0.5,0.5-1.9,2.6-1.9,5.8v3.1h4.7c0.9,0,1.6-0.7,1.6-1.6v-4.7h-3.1c0,0,0.1-0.7,0.6-1.4C-470,498.7-469.5,498.3-468.9,498.1z
+ M-480.1,498c-0.5,0.5-1.9,2.9-1.9,6v2.9h4.7c0.9,0,1.6-0.7,1.6-1.6v-4.7h-3.1c0,0,0.1-0.7,0.6-1.4c0.5-0.5,1-0.9,1.6-1.2
+ c0.2-0.1,0.5-0.2,0.6-0.5s0.2-0.5,0.2-0.9V496c-1,0.2-1.5,0.2-2.6,0.6C-479,497-479.6,497.5-480.1,498z"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-rtl.svg
new file mode 100644
index 00000000..b40a8ac9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotes-rtl.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-487 489 24 24" enable-background="new -487 489 24 24" xml:space="preserve">
+<g>
+ <path d="M-479.5,499.3c0.5,0.7,0.6,1.4,0.6,1.4h-3.1v4.7c0,0.9,0.7,1.6,1.6,1.6h4.7v-3.1c0-3.1-1.5-5.2-1.9-5.8
+ c-0.5-0.5-1-1-1.7-1.4c-1.1-0.5-1.6-0.5-2.6-0.6v0.8c0,0.3,0.1,0.6,0.2,0.9s0.4,0.4,0.6,0.5C-480.5,498.3-480,498.7-479.5,499.3z
+ M-471.7,496.6c-1.1-0.5-1.6-0.5-2.6-0.6v0.8c0,0.3,0.1,0.6,0.2,0.9s0.4,0.4,0.6,0.5c0.6,0.2,1.2,0.6,1.6,1.2
+ c0.5,0.7,0.6,1.4,0.6,1.4h-3.1v4.7c0,0.9,0.7,1.6,1.6,1.6h4.7V504c0-3.1-1.5-5.4-1.9-6C-470.4,497.5-471,497-471.7,496.6z"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-ltr.svg
new file mode 100644
index 00000000..24fca8f5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M3.5 8.6c-.6.7-2.5 3.4-2.5 7.4v4h6c1.1 0 2-.9 2-2v-6h-4s.1-.9.8-1.8c.6-.7 1.3-1.2 2.1-1.5.3-.1.6-.3.8-.6.2-.3.3-.7.3-1.1v-1c-1.3.2-1.9.2-3.3.8-.8.5-1.6 1.1-2.2 1.8zm15.5-3.6v-4h-2v4h-4v2h4v4h2v-4h4v-2zm-4 7s.1-.9.8-1.8l.2-.2v-2h-1.9l-.6.6c-.6.7-2.5 3.4-2.5 7.4v4h6c1.1 0 2-.9 2-2v-6h-4z" id="path6"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-rtl.svg
new file mode 100644
index 00000000..736f2a6d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/quotesAdd-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <path d="M20.5 8.6c.6.7 2.5 3.4 2.5 7.4v4h-6c-1.1 0-2-.9-2-2v-6h4s-.1-.9-.8-1.8c-.6-.7-1.3-1.2-2.1-1.5-.3-.1-.6-.3-.8-.6-.2-.3-.3-.7-.3-1.1v-1c1.3.2 1.9.2 3.3.8.8.5 1.6 1.1 2.2 1.8zm-15.5-3.6v-4h2v4h4v2h-4v4h-2v-4h-4v-2zm4 7s-.1-.9-.8-1.8l-.2-.2v-2h1.9l.6.6c.6.7 2.5 3.4 2.5 7.4v4h-6c-1.1 0-2-.9-2-2v-6h4z" id="path6"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-ltr.svg
new file mode 100644
index 00000000..884d40df
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-ltr.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="create_redirect">
+ <g>
+ <path d="M17.7 2.4c-.3-.3-.7-.4-1.2-.4h-12.1v16.2c0 .5.1.8.4 1.1s.7.7 1.2.7h10.2c-.6-.2-1.2-.5-1.9-1-.4-.3-.8-.6-1.2-1l-.5-.6h-6.2v-1.4h5.4s-.4-1.5-.4-2h-5v-1h9v1c.4.1 1.1.1 1.5.1.4 0 .7 0 1.1-.1v-10.5c.1-.5-.1-.9-.3-1.1zm-5.2 1.6h3v4.5h-3v-4.5zm-6.1 0h4v1.6h-4v-1.6zm0 3h4v1.5h-4v-1.5zm0 3h9v1.5h-9v-1.5zm12.7 3.1l4.9 3.8-4.9 4.8v-2.2c-1.7 0-2.9-.2-4.3-1.2-1.2-.8-2.5-2.6-2.3-4.1 1.4 1 2.9 1.5 4.4 1.5.7 0 1.4-.1 2.1-.3l.1-2.3"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-rtl.svg
new file mode 100644
index 00000000..a07e8364
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/redirect-rtl.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="create_redirect">
+ <g id="g3264">
+ <path d="M6.3 2.4c.3-.3.7-.4 1.2-.4h12.1v16.2c0 .5-.1.8-.4 1.1-.3.3-.7.7-1.2.7h-10.2c.6-.2 1.2-.5 1.9-1 .4-.3.8-.6 1.2-1l.5-.6h6.2v-1.4h-5.4s.4-1.5.4-2h5v-1h-9v1c-.4.1-1.1.1-1.5.1-.4 0-.7 0-1.1-.1v-10.5c-.1-.5.1-.9.3-1.1zm5.2 1.6h-3v4.5h3v-4.5zm6.1 0h-4v1.6h4v-1.6zm0 3h-4v1.5h4v-1.5zm0 3h-9v1.5h9v-1.5z" id="path3266"/>
+ <path d="M4.9 13.1l-4.9 3.8 4.9 4.8v-2.2c1.7 0 2.9-.2 4.3-1.2 1.2-.8 2.5-2.6 2.3-4.1-1.4 1-2.9 1.5-4.4 1.5-.7 0-1.4-.1-2.1-.3l-.1-2.3" id="path3268"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/regular-expression.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/regular-expression.svg
new file mode 100644
index 00000000..7b672618
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/regular-expression.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="regular-expression">
+ <path id="left-bracket" d="m 3,12.044797 c -5e-7,-0.989171 0.150394,-1.914889 0.451184,-2.7771612 C 3.7558785,8.4053812 4.1933899,7.6495032 4.7637193,7 L 6.2286026,7 C 5.6778034,7.7204251 5.261777,8.511764 4.9805221,9.3740188 4.6992623,10.236291 4.5586337,11.122815 4.5586357,12.033598 c -2e-6,0.914522 0.1425798,1.799179 0.427746,2.653974 C 5.2754491,15.538635 5.6856161,16.309444 6.2168835,17 L 4.7637193,17 C 4.1894835,16.365435 3.7519721,15.624488 3.451184,14.777158 3.150394,13.929828 3,13.019042 3,12.044797" />
+ <path id="dot" d="m 10,16 c 0,0.552285 -0.4477153,1 -1,1 -0.5522847,0 -1,-0.447715 -1,-1 0,-0.552285 0.4477153,-1 1,-1 0.5522847,0 1,0.447715 1,1 z" />
+ <path id="star" d="m 14.250652,7.0127142 -0.240235,2.15625 2.185547,-0.609375 0.193359,1.4765618 -1.992187,0.140625 1.306641,1.740234 -1.330079,0.708985 -0.914062,-1.833985 -0.802734,1.822266 -1.382813,-0.697266 1.294922,-1.740234 -1.980469,-0.152343 0.228516,-1.4648438 2.138672,0.609375 -0.240235,-2.15625 1.535157,0" />
+ <path id="right-bracket" d="m 21,12.044797 c -3e-6,0.981711 -0.152351,1.896229 -0.457043,2.743558 C 20.241767,15.635686 19.806209,16.3729 19.235883,17 l -1.453164,0 c 0.527356,-0.686824 0.93557,-1.455766 1.224642,-2.306829 0.289069,-0.854795 0.433604,-1.741318 0.433606,-2.659573 -2e-6,-0.910783 -0.140631,-1.797307 -0.421886,-2.6595792 C 18.737821,8.511764 18.321795,7.7204251 17.771,7 l 1.464883,0 c 0.574232,0.653236 1.011744,1.4128466 1.312536,2.2788341 0.300785,0.8622719 0.45118,1.7842569 0.451183,2.7659629" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/remove.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/remove.svg
new file mode 100644
index 00000000..6ad79174
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/remove.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="remove">
+ <path id="trash-can" d="M12 10h-1v6h1v-6zm-2 0h-1v6h1v-6zm4 0h-1v6h1v-6zm0-4v-1h-5v1h-3v3h1v7.966l1 1.031v-.074.077h6.984l.016-.018v.015l1-1.031v-7.966h1v-3h-3zm1 11h-7v-8h7v8zm1-9h-9v-1h9v1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ribbonPrize.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ribbonPrize.svg
new file mode 100644
index 00000000..6e4979f8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/ribbonPrize.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="Layer_1">
+ <g>
+ <circle cx="11.5" cy="8.5" r="2.5"/>
+ <path d="M16.3 8.7l.7-.7-.8-.8.4-.8-1.1-.5.1-.9-1.2-.2-.1-.9-1.2.2-.4-.8-1.1.5-.6-.8-.8.8-.9-.4-.5 1.1-.9-.2-.2 1.2-.9.2.2 1.2-.9.4.5 1.1-.6.6.8.8-.4.8 1.1.5-.1.9 1.2.2.1.9 1.2-.2.4.8 1.1-.5.6.8.8-.8.8.4.5-1.1.9.1.2-1.2.9-.1-.2-1.2.8-.4-.4-1zm-4.8 3.3c-1.9 0-3.5-1.6-3.5-3.5s1.6-3.5 3.5-3.5 3.5 1.6 3.5 3.5-1.6 3.5-3.5 3.5zm.5 3l-.7-.7-1.1.6-.4-.7-.8.3v8.5l2.5-3 2.5 3v-8.5l-1-.5z"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-ltr.svg
new file mode 100644
index 00000000..cdcbc30d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="search">
+ <path id="magnifying-glass" d="M 10.5,4 C 6.9101491,4 4,6.9101491 4,10.5 c 0,3.589851 2.9101491,6.5 6.5,6.5 1.02211,0 1.983324,-0.235899 2.84375,-0.65625 L 16,19 c 1.4,1.4 2.5,1.5 4,0 L 15.5625,14.5625 C 16.462737,13.447115 17,12.044969 17,10.5 17,6.9101491 14.089851,4 10.5,4 z m 0,2 C 12.985281,6 15,8.0147186 15,10.5 15,12.985281 12.985281,15 10.5,15 8.0147186,15 6,12.985281 6,10.5 6,8.0147186 8.0147186,6 10.5,6 z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-rtl.svg
new file mode 100644
index 00000000..c6753493
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/search-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="search">
+ <path id="magnifying-glass" d="m 13.5,4 c 3.589851,0 6.5,2.9101491 6.5,6.5 0,3.589851 -2.910149,6.5 -6.5,6.5 -1.02211,0 -1.983324,-0.235899 -2.84375,-0.65625 L 8,19 C 6.6,20.4 5.5,20.5 4,19 L 8.4375,14.5625 C 7.537263,13.447115 7,12.044969 7,10.5 7,6.9101491 9.910149,4 13.5,4 z m 0,2 C 11.014719,6 9,8.0147186 9,10.5 9,12.985281 11.014719,15 13.5,15 15.985281,15 18,12.985281 18,10.5 18,8.0147186 15.985281,6 13.5,6 z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/secure-link.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/secure-link.svg
new file mode 100644
index 00000000..a9c7d276
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/secure-link.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="secure">
+ <path id="lock" d="M8 5h.019v-.997c.001-.057.004-1.409-.832-2.255-.434-.438-.998-.66-1.679-.66s-1.245.222-1.678.659c-.837.847-.833 2.199-.832 2.251v1.002h.002c-.553 0-1 .447-1 1v3c0 .553.447 1 1 1h5c.553 0 1-.447 1-1v-3c0-.553-.447-1-1-1zm-4.002 0v-1.007c0-.01.005-.999.543-1.543.482-.485 1.449-.487 1.932-.002.544.546.546 1.536.546 1.55v1.002h-3.021z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/settings.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/settings.svg
new file mode 100644
index 00000000..bcd665ee
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/settings.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="settings">
+ <path id="gear" d="M3 4h3v2h-3zM12 4h9v2h-9zM8 3h2c.552 0 1 .448 1 1v2c0 .552-.448 1-1 1h-2c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1zM3 11h9v2h-9zM18 11h3v2h-3zM14 10h2c.552 0 1 .448 1 1v2c0 .552-.448 1-1 1h-2c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1zM3 18h6v2h-6zM15 18h6v2h-6zM11 17h2c.552 0 1 .448 1 1v2c0 .552-.448 1-1 1h-2c-.552 0-1-.448-1-1v-2c0-.552.448-1 1-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-ltr.svg
new file mode 100644
index 00000000..0d495049
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M0 20h24v1h-24v-1zm6-8l-1-1-2 2-2-2-1 1 2 2-2 2 1 1 2-2 2 2 1-1-2-2zm15.6 3.7c-.9-.5-1.9-.5-2.7 0-1.5.9-3.1.4-3.1.4-.4-.2-.8-.4-1.1-.6 2.2-.6 4.4-1.8 6-3.9 1.1-1.2 2.5-3.9.4-6-.7-.7-1.6-1.1-2.7-1-1.4.1-2.8.9-3.9 2.1-.9 1.1-3.1 4.5-2.3 7.5 0 .1 0 .2.1.3-2.3.3-4.2.2-4.4.1v1.5c.7.1 2.7.2 5.1-.2.5.7 1.3 1.2 2.3 1.6.1 0 2.4.8 4.5-.6.5-.3.9-.1 1.1 0 .4.2.7.6.7 1h1.4c0-.8-.6-1.7-1.4-2.2zm-8-1.7c-.5-2.2 1.1-5.1 2-6.2.8-.9 1.8-1.5 2.8-1.6h.1c.6 0 1.1.2 1.5.6 1.6 1.6-.4 3.9-.5 4-1.5 2-3.7 3-5.8 3.5l-.1-.3z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-rtl.svg
new file mode 100644
index 00000000..6c0ae5e0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/signature-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M24 20h-24v1h24v-1zm-6-8l1-1 2 2 2-2 1 1-2 2 2 2-1 1-2-2-2 2-1-1 2-2zm-15.6 3.7c.9-.5 1.9-.5 2.7 0 1.5.9 3.1.4 3.1.4.4-.2.8-.4 1.1-.6-2.2-.6-4.4-1.8-6-3.9-1.1-1.2-2.5-3.9-.4-6 .7-.7 1.6-1.1 2.7-1 1.4.1 2.8.9 3.9 2.1.9 1.1 3.1 4.5 2.3 7.5 0 .1 0 .2-.1.3 2.3.3 4.2.2 4.4.1v1.5c-.7.1-2.7.2-5.1-.2-.5.7-1.3 1.2-2.3 1.6-.1 0-2.4.8-4.5-.6-.5-.3-.9-.1-1.1 0-.4.2-.7.6-.7 1h-1.4c0-.8.6-1.7 1.4-2.2zm8-1.7c.5-2.2-1.1-5.1-2-6.2-.8-.9-1.8-1.5-2.8-1.6h-.1c-.6 0-1.1.2-1.5.6-1.6 1.6.4 3.9.5 4 1.5 2 3.7 3 5.8 3.5l.1-.3z" id="path576"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-ltr.svg
new file mode 100644
index 00000000..e8b427b1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+ <g id="down">
+ <path id="arrow" d="M22 3l-3.5 6L15 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-rtl.svg
new file mode 100644
index 00000000..e5e95196
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/smaller-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="a" d="M12.666 6h-1.372l-4.48 12H8.52l1.493-4h4l1.507 4h1.666l-4.52-12zm-2.28 7l1.617-4.333L13.637 13h-3.25z"/>
+ <g id="down">
+ <path id="arrow" d="M9 3L5.5 9 2 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/specialCharacter.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/specialCharacter.svg
new file mode 100644
index 00000000..4d601281
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/specialCharacter.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="special-character">
+ <path id="omega" d="M12 6.708c-.794 0-1.368.103-1.894.31-.525.207-.944.496-1.255.867-.311.366-.531.808-.66 1.327-.128.513-.192 1.08-.192 1.699 0 .513.058 1 .174 1.46.122.46.311.87.568 1.23.629.863 1.155 1.139 2.011 1.363l.247 3.035h-5v-3h.605l.531 1.354.394.053.605.044.751.035.456.009h.66l-.092-.894c-.629-.094-.811-.268-1.336-.522-.525-.26-.98-.59-1.365-.991-.379-.401-.675-.867-.889-1.398-.214-.537-.321-1.13-.321-1.779 0-.82.131-1.537.394-2.15.269-.619.656-1.133 1.163-1.54.507-.407 1.133-.711 1.878-.912.745-.206 1.6-.31 2.565-.31.959 0 1.811.103 2.556.31.751.201 1.38.504 1.887.912.507.407.892.92 1.154 1.54.269.614.403 1.33.403 2.15 0 .649-.107 1.242-.321 1.779-.214.531-.513.997-.898 1.398-.379.401-.831.732-1.356.991-.525.254-.707.428-1.336.522l-.092.894h.66l.447-.009.751-.035.605-.044.403-.053.531-1.354h.605v3h-5l.247-3.035c1.066-.11 1.337-.696 2.002-1.363.263-.36.452-.77.568-1.23.122-.46.183-.947.183-1.46 0-.619-.064-1.186-.192-1.699-.128-.519-.348-.962-.66-1.327-.311-.372-.73-.661-1.255-.867-.525-.206-1.1-.31-1.894-.31"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-ltr.svg
new file mode 100644
index 00000000..f3fb8b31
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M19 20h-17l3-3v-11h17v11c0 1.7-1.3 3-3 3z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-rtl.svg
new file mode 100644
index 00000000..fd9b7bd9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubble-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g586">
+ <path d="M5 20h17l-3-3v-11h-17v11c0 1.7 1.3 3 3 3z" id="path588"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-ltr.svg
new file mode 100644
index 00000000..333c1e86
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M19 20h-17l3-3v-11h17v11c0 1.7-1.3 3-3 3z"/>
+ </g>
+ <path fill="#fff" d="M13 9h1v7h-1zm-3 3h7v1h-7z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-rtl.svg
new file mode 100644
index 00000000..4e6313f1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbleAdd-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g598">
+ <path d="M5 20h17l-3-3v-11h-17v11c0 1.7 1.3 3 3 3z" id="path600"/>
+ </g>
+ <path d="M11 9h-1v7h1zm3 3h-7v1h7z" id="path602" fill="#fff"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-ltr.svg
new file mode 100644
index 00000000..c4b4a2f7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M20 9v9l2 2h-14v-11h12zm-17-5h12v4h-8v7h-6l2-2v-9z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-rtl.svg
new file mode 100644
index 00000000..c452fbbd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/speechBubbles-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g612">
+ <path d="M3 9v9l-2 2h14v-11h-12zm17-5h-12v4h8v7h6l-2-2v-9z" id="path614"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/star.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/star.svg
new file mode 100644
index 00000000..ea8c26c6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/star.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 7.4l1.7 3.6 4 .5-2.7 2.8.5 3.9-3.5-1.7-3.6 1.7.6-3.9-2.8-2.8 3.9-.5 1.9-3.6m0-3.4l-2.8 5.6-6.2.9 4.5 4.4-1.1 6.1 5.6-3 5.5 3-1-6.2 4.5-4.4-6.3-.9-2.7-5.5z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stop.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stop.svg
new file mode 100644
index 00000000..7bd06331
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stop.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 6c3.9 0 7 3.1 7 7s-3.1 7-7 7-7-3.1-7-7 3.1-7 7-7m0-1c-4.4 0-8 3.6-8 8s3.6 8 8 8 8-3.6 8-8-3.6-8-8-8zm-3 5h6v6h-6z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-a.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-a.svg
new file mode 100644
index 00000000..480189f5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-a.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="strikethrough-a">
+ <path id="strikethrough" d="M6 11h12v1h-12v-1z"/>
+ <path id="a" d="M12.666 6h-1.372l-4.48 12h1.705l1.494-4h3.999l1.508 4h1.666l-4.52-12zm-2.28 7l1.617-4.333 1.634 4.333h-3.251z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-s.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-s.svg
new file mode 100644
index 00000000..d57b652f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-s.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="strikethrough-s">
+ <path id="strikethrough" d="M6 12h12v1h-12v-1z"/>
+ <path id="s" d="M12.094 6c-1.133 0-2.076.287-2.75.9-.67.613-1 1.49-1 2.52 0 .889.221 1.602.719 2.13.498.528 1.279.91 2.312 1.14l.812.182v-.03c.656.147 1.128.375 1.375.63.252.256.375.607.375 1.11 0 .573-.172.97-.531 1.26-.358.291-.894.45-1.625.45-.477 0-.969-.074-1.469-.24-.502-.166-1.031-.417-1.562-.75l-.375-.238v2.158l.156.062c.58.237 1.143.417 1.688.54.549.121 1.07.18 1.562.18 1.286 0 2.297-.293 3-.9.709-.605 1.062-1.486 1.062-2.608 0-.943-.256-1.726-.781-2.312-.521-.592-1.305-1-2.344-1.229l-.812-.181c-.716-.148-1.204-.352-1.406-.539-.205-.203-.312-.485-.312-.935 0-.533.162-.899.5-1.17.342-.271.836-.42 1.531-.42.395 0 .818.052 1.25.181.433.127.908.333 1.406.6l.375.18v-2.041s-1.188-.383-1.688-.479c-.499-.098-.984-.151-1.468-.151z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-y.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-y.svg
new file mode 100644
index 00000000..8409dc15
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/strikethrough-y.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="strikethrough-y">
+ <path id="strikethrough" d="M6 11h12v1h-12v-1z"/>
+ <path id="a" d="M7 6h1.724l3.288 4.935 3.264-4.935h1.724l-4.194 6.285v5.715h-1.612v-5.715l-4.194-6.285"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-ltr.svg
new file mode 100644
index 00000000..acacc362
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M4 9h12v2h-12v-2zm0 3h8v2h-8v-2zm0-7h16v3h-16v-3zm16 14h-16v-3h16v3z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-rtl.svg
new file mode 100644
index 00000000..c38a283f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeFlow-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M20 9h-12v2h12v-2zm0 3h-8v2h8v-2zm0-7h-16v3h16v-3zm-16 14h16v-3h-16v3z" id="path624"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSideMenu.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSideMenu.svg
new file mode 100644
index 00000000..47e70d74
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSideMenu.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M20 19h-16v-2h16v2z"/>
+ </g>
+ <g>
+ <path d="M20 15h-16v-2h16v2z"/>
+ </g>
+ <g>
+ <path d="M20 11h-16v-2h16v2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-ltr.svg
new file mode 100644
index 00000000..7f8822bc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M20 11h-16v-2h16v2zm-16 1h8v2h-8v-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-rtl.svg
new file mode 100644
index 00000000..fcb10bad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeSummary-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g654">
+ <path d="M4 11h16v-2h-16v2zm16 1h-8v2h8v-2z" id="path656"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-ltr.svg
new file mode 100644
index 00000000..76c80d20
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M17 13h-13v-3h13v3zm-5 6h-8v-3h8v3zm-8-12v-3h16v3h-16z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-rtl.svg
new file mode 100644
index 00000000..308c2e62
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/stripeToC-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g666">
+ <path d="M7 13h13v-3h-13v3zm5 6h8v-3h-8v3zm8-12v-3h-16v3h16z" id="path668"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-ltr.svg
new file mode 100644
index 00000000..b7507daf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-ltr.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+ <path d="M18 13l-1 1v3l1 1h-1l-.527-.46L16 18h-1l1-1v-3l-1-1h1l.485.497L17 13z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-rtl.svg
new file mode 100644
index 00000000..9fe5325f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/subscript-rtl.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+ <path d="M8 13l1 1v3l-1 1h1l.527-.46L10 18h1l-1-1v-3l1-1h-1l-.485.497L9 13z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-ltr.svg
new file mode 100644
index 00000000..f1b7caf3
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-ltr.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M18.1 5.1c0 .3-.1.6-.3.9l-1.4 1.4-.9-.8 2.2-2.2c.3.1.4.4.4.7zm-.5 5.3h3.2c0 .3-.1.6-.4.9s-.5.4-.8.4h-2v-1.3zm-6.2-5v-3.2c.3 0 .6.1.9.4s.4.5.4.8v2h-1.3zm6.4 11.7c-.3 0-.6-.1-.8-.3l-1.4-1.4.8-.8 2.2 2.2c-.2.2-.5.3-.8.3zm-11.6-12.2c.3 0 .6.1.8.3l1.4 1.4-.8.9-2.2-2.3c.2-.2.5-.3.8-.3zm5.2 11.7h1.2v3.2c-.3 0-.6-.1-.9-.4s-.4-.5-.4-.8l.1-2zm-7-6.2h2v1.2h-3.2c0-.3.1-.6.4-.9s.5-.3.8-.3zm1.8 5.6l1.4-1.4.8.8-2.2 2.2c-.2-.2-.3-.5-.3-.8s.1-.6.3-.8z"/>
+ <circle cx="12" cy="11" r="4"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-rtl.svg
new file mode 100644
index 00000000..a625fb90
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/sun-rtl.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M5.9 5.1c0 .3.1.6.3.9l1.4 1.4.9-.8-2.2-2.2c-.3.1-.4.4-.4.7zm.5 5.3h-3.2c0 .3.1.6.4.9.3.3.5.4.8.4h2v-1.3zm6.2-5v-3.2c-.3 0-.6.1-.9.4-.3.3-.4.5-.4.8v2h1.3zm-6.4 11.7c.3 0 .6-.1.8-.3l1.4-1.4-.8-.8-2.2 2.2c.2.2.5.3.8.3zm11.6-12.2c-.3 0-.6.1-.8.3l-1.4 1.4.8.9 2.2-2.3c-.2-.2-.5-.3-.8-.3zm-5.2 11.7h-1.2v3.2c.3 0 .6-.1.9-.4.3-.3.4-.5.4-.8l-.1-2zm7-6.2h-2v1.2h3.2c0-.3-.1-.6-.4-.9-.3-.3-.5-.3-.8-.3zm-1.8 5.6l-1.4-1.4-.8.8 2.2 2.2c.2-.2.3-.5.3-.8 0-.3-.1-.6-.3-.8z" id="path678"/>
+ <circle cx="12" cy="11" r="4" id="circle680" transform="matrix(-1 0 0 1 24 0)"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-ltr.svg
new file mode 100644
index 00000000..39f30a76
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-ltr.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M14 9l-2.354 3.406L14 16h-1.2L11 13.25 9.2 16H8l2.403-3.662L8 9h1.188l1.857 2.494L12.797 9H14z"/>
+ <path d="M18 7l-1 1v3l1 1h-1l-.527-.46L16 12h-1l1-1V8l-1-1h1l.485.497L17 7z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-rtl.svg
new file mode 100644
index 00000000..eabab21c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/superscript-rtl.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path id="x" d="M12 9l2.354 3.406L12 16h1.2l1.8-2.75L16.8 16H18l-2.403-3.662L18 9h-1.188l-1.857 2.494L13.203 9H12z"/>
+ <path d="M8 7l1 1v3l-1 1h1l.527-.46L10 12h1l-1-1V8l1-1h-1l-.485.497L9 7z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-caption.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-caption.svg
new file mode 100644
index 00000000..15bb06a6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-caption.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-caption">
+ <path id="caption" d="M6 6h12v3H6z"/>
+ <path id="table" d="M4 10v7h16v-7H4zm1 1h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2zM5 14h4v2H5v-2zm5 0h4v2h-4v-2zm5 0h4v2h-4v-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-ltr.svg
new file mode 100644
index 00000000..798ee4a1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-ltr.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-column-ltr">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 5,5 2,0 0,14 -2,0 z"
+ id="column" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-rtl.svg
new file mode 100644
index 00000000..dfa33a08
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-column-rtl.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-column-rtl">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 17,5 2,0 0,14 -2,0 z"
+ id="column" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-after.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-after.svg
new file mode 100644
index 00000000..91d06644
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-after.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-row-after">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 5,17 14,0 0,2 -14,0 z"
+ id="row" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-before.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-before.svg
new file mode 100644
index 00000000..4b71f2a8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-insert-row-before.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-insert-row-before">
+ <path
+ d="m 13,9 -2,0 0,2 -2,0 0,2 2,0 0,2 2,0 0,-2 2,0 0,-2 -2,0 z"
+ id="plus" />
+ <path
+ d="m 5,5 14,0 0,2 -14,0 z"
+ id="row" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-merge-cells.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-merge-cells.svg
new file mode 100644
index 00000000..6a8b77d8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table-merge-cells.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24">
+ <g id="table-merge-cells">
+ <g id="merge-cell-left">
+ <path id="cell-border" d="m 4,7 0,9 7,0 0,-3 -1,0.834 L 10,15 5,15 5,8 10,8 10,9.167 11,10 11,7 z" />
+ <path id="arrow" d="m 8,9 0,2 -2,0 0,1 2,0 0,2 3,-2.5 z" />
+ </g>
+ <use id="merge-cell-right" xlink:href="#merge-cell-left" transform="matrix(-1,0,0,1,24,0)" />
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table.svg
new file mode 100644
index 00000000..1ba8c440
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/table.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+
+ <g id="table-insert">
+ <path id="table" d="M4 5v13h16v-13zm2 2h5v4h-5zm7 0h5v4h-5zm-7 5h5v4h-5zm7 0h5v4h-5z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/tag.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/tag.svg
new file mode 100644
index 00000000..534824c8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/tag.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="tag">
+ <path d="M18.748 11.717c.389.389.389 1.025 0 1.414l-4.949 4.95c-.389.389-1.025.389-1.414 0l-6.01-6.01c-.389-.389-.707-1.157-.707-1.707l-.001-4.364c0-.55.45-1 1-1h4.364c.55 0 1.318.318 1.707.707l6.01 6.01zm-10.644-4.261c-.579.576-.578 1.514-.001 2.093.578.577 1.516.577 2.095.001.576-.578.576-1.517 0-2.095-.581-.576-1.518-.577-2.094.001z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-ltr.svg
new file mode 100644
index 00000000..6b594b29
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-ltr.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M24 6h-4v-4h-2v4h-4v2h4v4h2v-4h4z"/>
+ </g>
+ <path d="M19 13v7h-16c-1.1 0-2-.9-2-2v-11h12v-1h-13v12c0 1.7 1.3 3 3 3h17v-8h-1z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-rtl.svg
new file mode 100644
index 00000000..36b25a3e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/templateAdd-rtl.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g690">
+ <path d="M0 6h4v-4h2v4h4v2h-4v4h-2v-4h-4z" id="path692"/>
+ </g>
+ <path d="M5 13v7h16c1.1 0 2-.9 2-2v-11h-12v-1h13v12c0 1.7-1.3 3-3 3h-17v-8h1z" id="path694"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-lefttoright.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-lefttoright.svg
new file mode 100644
index 00000000..62526a03
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-lefttoright.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="text-dir-ltr">
+ <path d="M7 7h-2v-1h2l.469.5.531-.5h2v1h-2v10h2v1h-2l-.5-.531-.5.531h-2v-1h2zM13.976 16v-2h-2.976v-4h2.976v-1.956l6.024 3.978z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-righttoleft.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-righttoleft.svg
new file mode 100644
index 00000000..913bbfd6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-dir-righttoleft.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="text-dir-rtl">
+ <path d="M17 17h2v1h-2l-.469-.5-.531.5h-2v-1h2v-10h-2v-1h2l.5.531.5-.531h2v1h-2zM10.024 8v2h2.976v4h-2.976v1.956l-6.024-3.978z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-style.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-style.svg
new file mode 100644
index 00000000..0198c355
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/text-style.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="text-style">
+ <path id="a" d="M15.296 18h2.789l-1.14-12h-2.789l-8.156 12h2.789l2.039-3h4.183l.285 3zm-3.109-5l2.311-3.4.323 3.4h-2.634z"/>
+ <path id="underline" d="M6 19h12v1h-12v-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-ltr.svg
new file mode 100644
index 00000000..7740e43e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M11.1 13.1c-1.8-2.1-2.7-4.3-3-5.1h4.7l.7-2h-5.5v-3h-2v3h-5v2h5c-.2.9-1.3 4.8-5.1 7.6l1.2 1.6c2.7-2 4.3-4.5 5.1-6.4.7 1.3 1.7 3 3.2 4.5l.7-2.2zm1.4 6.9l1.3-4h5.3l1.3 4h2.2l-4.6-14h-3l-4.7 14h2.2zm4-12l2 6h-4l2-6z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-rtl.svg
new file mode 100644
index 00000000..c78e6222
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/translation-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12.4 13.1c1.8-2.1 2.7-4.3 3-5.1h-4.7l-.7-2h5.5v-3h2v3h5v2h-5c.2.9 1.3 4.8 5.1 7.6l-1.2 1.6c-2.7-2-4.3-4.5-5.1-6.4-.7 1.3-1.7 3-3.2 4.5l-.7-2.2zm-1.4 6.9l-1.3-4h-5.3l-1.3 4h-2.2l4.6-14h3l4.7 14h-2.2zm-4-12l-2 6h4l-2-6z" id="path704"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trash.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trash.svg
new file mode 100644
index 00000000..f5914312
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trash.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M6 8c0-1.1.9-2 2-2h2l1-1h2l1 1h2c1.1 0 2 .9 2 2h-12zm1 1h10l-1 11h-8z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-ltr.svg
new file mode 100644
index 00000000..0731f056
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M20.5 20.5l-15.5-15.5-1 1 3 3 1 11h8l.2-1.8 3.3 3.3zm-3.5-11.5h-6l5.5 5.5zm1-1c0-1.1-.9-2-2-2h-2l-1-1h-2l-1 1h-2l2 2h8z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-rtl.svg
new file mode 100644
index 00000000..2a92cbef
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/trashUndo-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g714">
+ <path d="M4 20.5l15.5-15.5 1 1-3 3-1 11h-8l-.2-1.8-3.3 3.3zm3.5-11.5h6l-5.5 5.5zm-1-1c0-1.1.9-2 2-2h2l1-1h2l1 1h2l-2 2h-8z" id="path716"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-ltr.svg
new file mode 100644
index 00000000..66c024a9
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 9v-2s0-5-4.5-5-4.5 5-4.5 5h2s0-3 2.5-3 2.5 3 2.5 3v2h-3v7c0 1.7 1.3 3 3 3h10v-10z" id="path726"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-rtl.svg
new file mode 100644
index 00000000..07cecbfe
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unLock-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M11 9v-2s0-5 4.5-5 4.5 5 4.5 5h-2s0-3-2.5-3-2.5 3-2.5 3v2h3v7c0 1.7-1.3 3-3 3h-10v-10z" id="path726"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unStar.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unStar.svg
new file mode 100644
index 00000000..724d1901
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/unStar.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M21 11l-6-1-3-6-3 6-6 1 4 4-1 6 6-3 6 3-1-6 4-4z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-a.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-a.svg
new file mode 100644
index 00000000..dd6dde36
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-a.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="underline-a">
+ <path id="a" d="M14.424 16h2.076l-3.463-10h-2.077l-3.46 10h2.077l.627-2h3.604l.616 2zm-3.921-3.623l1.496-4.379 1.511 4.379h-3z"/>
+ <path id="underline" d="M7 17h10v1h-10v-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-u.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-u.svg
new file mode 100644
index 00000000..fbd7c147
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/underline-u.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="underline-u">
+ <path id="u" d="M8 6h2v5.959c-.104 1.707.695 2.002 2 2.041 1.777.062 2.002-.879 2-2.041v-5.959h2v6.123c0 1.279-.338 2.245-1.016 2.898-.672.651-1.666.979-2.98.979-1.32 0-2.319-.326-2.996-.979-.672-.653-1.008-1.619-1.008-2.898v-6.123"/>
+ <path id="underline" d="M7 17h10v1h-10v-1z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upTriangle.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upTriangle.svg
new file mode 100644
index 00000000..9e5e72f6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upTriangle.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M12 8l8 10h-16z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-ltr.svg
new file mode 100644
index 00000000..18879e32
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M10 13c0 1.7 1.3 3 3 3v-7h3l-4.5-5-4.5 5h3v4zm7 0v5h-10c-.6 0-1-.4-1-1v-4h-2v4c0 1.9 1.3 3 3 3h12v-7h-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-rtl.svg
new file mode 100644
index 00000000..7a3535ba
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/upload-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g736">
+ <path d="M13 13c0 1.7-1.3 3-3 3v-7h-3l4.5-5 4.5 5h-3v4zm-7 0v5h10c.6 0 1-.4 1-1v-4h2v4c0 1.9-1.3 3-3 3h-12v-7h2z" id="path738"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-ltr.svg
new file mode 100644
index 00000000..5dcc3179
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M16 5h-12v12c0 1.7 1.3 3 3 3h12v-12c0-1.7-1.3-3-3-3zm-2 4c.7 0 1.2.6 1.2 1.2s-.6 1.2-1.2 1.2-1.2-.6-1.2-1.2.5-1.2 1.2-1.2zm-5 0c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2.5-1.3 1.2-1.3zm7 5.4c0 .2-.1.3-.3.5-.7.6-1.6 1-2.6 1.3s-2.1.2-3.1 0-2-.9-2.7-1.5c-.1-.1-.2-.3-.2-.4s.1-.3.2-.4c.1-.1.3-.2.4-.2.2 0 .3.1.4.2.5.5 1.2.9 2.1 1.1s1.7.2 2.6 0 1.6-.5 2.1-1c.1-.1.3-.2.4-.2s.3.1.5.2.2.2.2.4z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-rtl.svg
new file mode 100644
index 00000000..a5e4dc95
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userActive-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M7 5h12v12c0 1.7-1.3 3-3 3h-12v-12c0-1.7 1.3-3 3-3zm2 4c-.7 0-1.2.6-1.2 1.2s.6 1.2 1.2 1.2 1.2-.6 1.2-1.2-.5-1.2-1.2-1.2zm5 0c-.7 0-1.2.6-1.2 1.2s.5 1.3 1.2 1.3 1.2-.6 1.2-1.2-.5-1.3-1.2-1.3zm-7 5.4c0 .2.1.3.3.5.7.6 1.6 1 2.6 1.3 1 .3 2.1.2 3.1 0s2-.9 2.7-1.5c.1-.1.2-.3.2-.4 0-.1-.1-.3-.2-.4-.1-.1-.3-.2-.4-.2-.2 0-.3.1-.4.2-.5.5-1.2.9-2.1 1.1-.9.2-1.7.2-2.6 0-.9-.2-1.6-.5-2.1-1-.1-.1-.3-.2-.4-.2-.1 0-.3.1-.5.2s-.2.2-.2.4z" id="path748"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userAvatar.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userAvatar.svg
new file mode 100644
index 00000000..e9687fa6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userAvatar.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g4">
+ <g id="g6">
+ <path d="M11.5 13c1.7 0 3.5-2 3.5-5 0-.1 0-4-3.5-4s-3.5 3.9-3.5 4c0 3 1.8 5 3.5 5zm3.5-1c-.4.7-1.7 2-3.5 2s-3.2-1.3-3.5-2h-2c-1.1 0-2 .9-2 2v6h15v-6c0-1.1-.9-2-2-2h-2z" id="path8"/>
+ </g>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-ltr.svg
new file mode 100644
index 00000000..bb5b0968
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M16 5h-12v12c0 1.7 1.3 3 3 3h12v-12c0-1.7-1.3-3-3-3zm-9.3 5.4c-.5-.4-.7-.8-.7-1.4.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6zm8.4 4.3c0 .2-.1.3-.3.4-1 .6-2.2.9-3.5.9-1.2 0-2.3-.3-3.3-1-.2-.1-.2-.2-.3-.4s0-.3.1-.5.2-.2.4-.3.3 0 .5.1c.8.5 1.7.8 2.8.8s2-.2 2.8-.7c.1-.1.3-.1.5-.1s.3.1.4.3l-.1.5zm1.2-4.3c-.5.4-1.1.6-1.8.6s-1.3-.2-1.8-.6-.7-.8-.7-1.4c.6.6 1.5.9 2.5.9s1.9-.3 2.5-.9c0 .6-.2 1-.7 1.4z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-rtl.svg
new file mode 100644
index 00000000..4a9fd0d7
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userInactive-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M7 5h12v12c0 1.7-1.3 3-3 3h-12v-12c0-1.7 1.3-3 3-3zm9.3 5.4c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9s-1.9-.3-2.5-.9c0 .6.2 1 .7 1.4.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6zm-8.4 4.3c0 .2.1.3.3.4 1 .6 2.2.9 3.5.9 1.2 0 2.3-.3 3.3-1 .2-.1.2-.2.3-.4.1-.2 0-.3-.1-.5s-.2-.2-.4-.3c-.2-.1-.3 0-.5.1-.8.5-1.7.8-2.8.8-1.1 0-2-.2-2.8-.7-.1-.1-.3-.1-.5-.1s-.3.1-.4.3l.1.5zm-1.2-4.3c.5.4 1.1.6 1.8.6s1.3-.2 1.8-.6c.5-.4.7-.8.7-1.4-.6.6-1.5.9-2.5.9s-1.9-.3-2.5-.9c0 .6.2 1 .7 1.4z" id="path758"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-ltr.svg
new file mode 100644
index 00000000..f516539c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-ltr.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M5 6v11l-3 3h17c1.7 0 3-1.3 3-3v-11h-17zm11.2 2.5c.7 0 1.2.6 1.2 1.2s-.5 1.3-1.2 1.3-1.2-.6-1.2-1.2.6-1.3 1.2-1.3zm-5.4 0c.7 0 1.2.6 1.2 1.2s-.6 1.3-1.2 1.3-1.2-.6-1.2-1.2.5-1.3 1.2-1.3zm2.7 8.5c-5.1 0-6-5-6-5s2 1 6 1l6-1s-1 5-6 5z" id="path6"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-rtl.svg
new file mode 100644
index 00000000..8963fafc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/userTalk-rtl.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M19 6v11l3 3h-17c-1.7 0-3-1.3-3-3v-11h17zm-11.2 2.5c-.7 0-1.2.6-1.2 1.2s.5 1.3 1.2 1.3 1.2-.6 1.2-1.2-.6-1.3-1.2-1.3zm5.4 0c-.7 0-1.2.6-1.2 1.2s.6 1.3 1.2 1.3 1.2-.6 1.2-1.2-.5-1.3-1.2-1.3zm-2.7 8.5c5.1 0 6-5 6-5s-2 1-6 1l-6-1s1 5 6 5z" id="path770"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewCompact.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewCompact.svg
new file mode 100644
index 00000000..d96a2e3f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewCompact.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="viewCompact">
+ <circle cx="6" cy="6" r="2"/>
+ <circle cx="12" cy="6" r="2"/>
+ <circle cx="18" cy="6" r="2"/>
+ <circle cx="6" cy="12" r="2"/>
+ <circle cx="12" cy="12" r="2"/>
+ <circle cx="18" cy="12" r="2"/>
+ <circle cx="6" cy="18" r="2"/>
+ <circle cx="12" cy="18" r="2"/>
+ <circle cx="18" cy="18" r="2"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-ltr.svg
new file mode 100644
index 00000000..4f5f9b3d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-ltr.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="viewDetails">
+ <circle cx="5.5" cy="8.5" r="2.5"/>
+ <path d="M10 6h12v1H10zM10 8h9v1h-9zM10 10h4v1h-4z"/>
+ <circle cx="5.5" cy="16.5" r="2.5"/>
+ <path d="M10 14h12v1H10zM10 16h9v1h-9zM10 18h4v1h-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-rtl.svg
new file mode 100644
index 00000000..f43b05f1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/viewDetails-rtl.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="viewDetails">
+ <circle cx="18.5" cy="8.5" r="2.5"/>
+ <path d="M14 6H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
+ <circle cx="18.5" cy="16.5" r="2.5"/>
+ <path d="M14 14H2v1h12zm0 2H5v1h9zm0 2h-4v1h4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/visionSimulator.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/visionSimulator.svg
new file mode 100644
index 00000000..ae6ba27a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/visionSimulator.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <path d="M0 10v8h2.3c.3.6 1 1 1.7 1h4c1.5 0 2.7-.8 3-2h2c.3 1.2 1.5 2 3 2h4c.7 0 1.4 0 1.7-1H24v-8zm10 6c0 1-.4 2-2 2H4c-.6 0-1-.4-1-1v-3c0-.6.4-1 1-1h5c.6 0 1 .4 1 1zm11 1c0 .6-.4 1-1 1h-4c-1.6 0-2-1-2-2v-2c0-.6.4-1 1-1h5c.6 0 1 .4 1 1z"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-ltr.svg
new file mode 100644
index 00000000..79c7d5ca
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M13 14h5v1h-5v-1zm0 3h5v-1h-5v1zm0 1h5v1h-5v-1zm-1-5v3l-5 3 1-6-4-3 6-1 2-5s1.9 5 2 5l6 1-4 3h-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-rtl.svg
new file mode 100644
index 00000000..6bbc2fa2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/watchlist-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g780">
+ <path d="M11 14h-5v1h5v-1zm0 3h-5v-1h5v1zm0 1h-5v1h5v-1zm1-5v3l5 3-1-6 4-3-6-1-2-5s-1.9 5-2 5l-6 1 4 3h4z" id="path782"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikiText.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikiText.svg
new file mode 100644
index 00000000..eebd9b1a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikiText.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M23 5h-4v2h2v10h-2v2h4z"/>
+ </g>
+ <g>
+ <path d="M18 5h-4v2h2v10h-2v2h4z"/>
+ </g>
+ <g>
+ <path d="M2 5h4v2h-2v10h2v2h-4z"/>
+ </g>
+ <g>
+ <path d="M7 5h4v2h-2v10h2v2h-4z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-ltr.svg
new file mode 100644
index 00000000..c606becd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-ltr.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g>
+ <path d="M15 9l.7-1.8c.9.4 1.8.7 2.4.9l-.6 1.7v.2l-2.5-1zm-4.3-1.9l.8-1.8c1.2.5 2.6 1.1 3 1.4l-.8 1.8-3-1.4zm-5.9-1c-.8 0-1.4.2-2 .6l-1.1-1.7c.9-.6 1.9-.9 3.1-.9v2zm-4.3.7l1.8.8c-.3.7-.3 1.3-.1 1.8l-1.9.7c-.3-1.2-.3-2.3.2-3.3zm4.2 5.4l-1.3 1.5c-1-1-1.7-1.6-2-2l1.5-1.3c.7.8 1.3 1.4 1.8 1.8zm7.3 4.3c0 1.9-1.6 3.5-3.5 3.5s-3.5-1.6-3.5-3.5 1.6-3.5 3.5-3.5 3.5 1.6 3.5 3.5z"/>
+ </g>
+ <path d="M24 8l-1-1-1.5 1.5-1.5-1.5-1 1 1.5 1.5-1.5 1.5 1 1 1.5-1.5 1.5 1.5 1-1-1.5-1.5z"/>
+ <circle cx="8" cy="5" r="2"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-rtl.svg
new file mode 100644
index 00000000..f304f6ed
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/wikitrail-rtl.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="g792">
+ <path d="M9.095 9l-.7-1.8c-.9.4-1.8.7-2.4.9l.6 1.7v.2l2.5-1zm4.3-1.9l-.8-1.8c-1.2.5-2.6 1.1-3 1.4l.8 1.8 3-1.4zm5.9-1c.8 0 1.4.2 2 .6l1.1-1.7c-.9-.6-1.9-.9-3.1-.9v2zm4.3.7l-1.8.8c.3.7.3 1.3.1 1.8l1.9.7c.3-1.2.3-2.3-.2-3.3zm-4.2 5.4l1.3 1.5c1-1 1.7-1.6 2-2l-1.5-1.3c-.7.8-1.3 1.4-1.8 1.8zm-7.3 4.3c0 1.9 1.6 3.5 3.5 3.5s3.5-1.6 3.5-3.5-1.6-3.5-3.5-3.5-3.5 1.6-3.5 3.5z" id="path794"/>
+ </g>
+ <path d="M.095 8l1-1 1.5 1.5 1.5-1.5 1 1-1.5 1.5 1.5 1.5-1 1-1.5-1.5-1.5 1.5-1-1 1.5-1.5z" id="path796"/>
+ <circle cx="8" cy="5" r="2" id="circle798" transform="matrix(-1 0 0 1 24.095 0)"/>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/window.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/window.svg
new file mode 100644
index 00000000..cd3b76c2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/icons/window.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+ <g id="window">
+ <path id="title" d="M7 10h10v1h-10z"/>
+ <path id="frame" d="M16 19h-8c-2.206 0-4-1.794-4-4v-6c0-2.206 1.794-4 4-4h8c2.206 0 4 1.794 4 4v6c0 2.206-1.794 4-4 4zm-8-12c-1.103 0-2 .897-2 2v6c0 1.103.897 2 2 2h8c1.103 0 2-.897 2-2v-6c0-1.103-.897-2-2-2h-8z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/alert.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/alert.svg
new file mode 100644
index 00000000..d9dc6a87
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/alert.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="alert">
+ <path d="M6 12c-3.314 0-6-2.686-6-6s2.686-6 6-6 6 2.686 6 6-2.686 6-6 6zm-1-5h2v-5h-2zm0 3h2v-2h-2z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-down.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-down.svg
new file mode 100644
index 00000000..17380577
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-down.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 1000 1000" enable-background="new 0 0 1000 1000" xml:space="preserve">
+<g id="down">
+ <path id="arrow" d="M883.3,341H116.7L500,724.3l0,0l0,0L883.3,341"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-ltr.svg
new file mode 100644
index 00000000..fb366e64
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-ltr.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-493 495 12 12" enable-background="new -493 495 12 12" xml:space="preserve">
+<g id="ltr">
+ <path id="arrow" d="M-489,496v10l5-5h0h0L-489,496"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-rtl.svg
new file mode 100644
index 00000000..62b6bb50
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-rtl.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-493 495 12 12" enable-background="new -493 495 12 12" xml:space="preserve">
+<g id="rtl">
+ <path id="arrow" d="M-485,506v-10l-5,5h0h0L-485,506"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-up.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-up.svg
new file mode 100644
index 00000000..20e734fb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/arrow-up.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="-493 495 12 12" enable-background="new -493 495 12 12" xml:space="preserve">
+<g id="up">
+ <path id="arrow" d="M-492,503h10l-5-5v0v0L-492,503"/>
+</g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/required.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/required.svg
new file mode 100644
index 00000000..969fa2d8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/required.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="required">
+ <path d="M5 1h2v10h-2zM9.83 2.634l1 1.732-8.66 5-1-1.732zM1.17 4.366l1-1.732 8.66 5-1 1.732z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-ltr.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-ltr.svg
new file mode 100644
index 00000000..266349ed
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-ltr.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="search">
+ <path id="path3051" d="M10.369 9.474l-2.374-2.375-.169-.099c.403-.566.643-1.26.643-2.009-.001-1.92-1.558-3.477-3.477-3.477-1.921 0-3.478 1.557-3.478 3.478 0 1.92 1.557 3.477 3.478 3.477.749 0 1.442-.239 2.01-.643l.098.169 2.375 2.374c.19.189.543.143.79-.104s.293-.601.104-.791zm-5.377-2.27c-1.221 0-2.213-.991-2.213-2.213 0-1.221.992-2.213 2.213-2.213 1.222 0 2.213.992 2.213 2.213-.001 1.222-.992 2.213-2.213 2.213z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-rtl.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-rtl.svg
new file mode 100644
index 00000000..5368fd7c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/indicators/search-rtl.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 12 12">
+ <g id="search">
+ <path id="path3051" d="M1.631 9.474l2.374-2.375.169-.099c-.403-.566-.643-1.26-.643-2.009.001-1.92 1.558-3.477 3.477-3.477 1.921 0 3.478 1.557 3.478 3.478 0 1.92-1.557 3.477-3.478 3.477-.749 0-1.442-.239-2.01-.643l-.098.169-2.375 2.374c-.19.189-.543.143-.79-.104s-.293-.601-.104-.791zm5.377-2.27c1.221 0 2.213-.991 2.213-2.213 0-1.221-.992-2.213-2.213-2.213-1.222 0-2.213.992-2.213 2.213.001 1.222.992 2.213 2.213 2.213z"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/pending.gif b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/pending.gif
new file mode 100644
index 00000000..1194eed2
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/pending.gif
Binary files differ
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/transparency.svg b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/transparency.svg
new file mode 100644
index 00000000..63a0b57c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/images/textures/transparency.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0" y="0" width="16" height="16" viewBox="0, 0, 16, 16">
+ <g id="transparency">
+ <path d="M0,0 L8,0 L8,8 L0,8 z" fill="#CCCCCC"/>
+ <path d="M8,8 L16,8 L16,16 L8,16 z" fill="#CCCCCC"/>
+ <path d="M8,0 L16,0 L16,8 L8,8 z" fill="#FFFFFF"/>
+ <path d="M0,8 L8,8 L8,16 L0,16 z" fill="#FFFFFF"/>
+ </g>
+</svg>
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/indicators.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/indicators.json
new file mode 100644
index 00000000..d83e57e6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/indicators.json
@@ -0,0 +1,29 @@
+{
+ "selectorWithoutVariant": ".oo-ui-indicator-{name}",
+ "selectorWithVariant": ".oo-ui-image-{variant} .oo-ui-indicator-{name}, .oo-ui-image-{variant}.oo-ui-indicator-{name}",
+ "intro": "@import '../../../../src/styles/common';",
+ "variants": {
+ "invert": {
+ "color": "#FFFFFF",
+ "global": true
+ }
+ },
+ "images": {
+ "alert": { "file": "images/indicators/alert.svg" },
+ "up": { "file": "images/indicators/arrow-up.svg" },
+ "down": { "file": "images/indicators/arrow-down.svg" },
+ "next": { "file": {
+ "ltr": "images/indicators/arrow-ltr.svg",
+ "rtl": "images/indicators/arrow-rtl.svg"
+ } },
+ "previous": { "file": {
+ "ltr": "images/indicators/arrow-rtl.svg",
+ "rtl": "images/indicators/arrow-ltr.svg"
+ } },
+ "required": { "file": "images/indicators/required.svg" },
+ "search": { "file": {
+ "ltr": "images/indicators/search-ltr.svg",
+ "rtl": "images/indicators/search-rtl.svg"
+ } }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/layouts.less b/vendor/oojs/oojs-ui/src/themes/mediawiki/layouts.less
new file mode 100644
index 00000000..c4956f33
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/layouts.less
@@ -0,0 +1,135 @@
+@import 'common';
+
+.theme-oo-ui-layout () {}
+
+.theme-oo-ui-bookletLayout () {
+ &-stackLayout {
+ > .oo-ui-panelLayout {
+ padding: 1.5em;
+ }
+ }
+
+ &-outlinePanel {
+ border-right: 1px solid #ddd;
+
+ > .oo-ui-outlineControlsWidget {
+ box-shadow: 0 0 0.25em rgba(0,0,0,0.25);
+ }
+ }
+}
+
+.theme-oo-ui-indexLayout () {
+ &-stackLayout {
+ > .oo-ui-panelLayout {
+ padding: 1.5em;
+ }
+ }
+}
+
+.theme-oo-ui-fieldLayout () {
+ margin-bottom: 1em;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+
+ &.oo-ui-fieldLayout-align-left,
+ &.oo-ui-fieldLayout-align-right {
+ &.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding-top: 0.5em;
+ margin-right: 5%;
+ width: 35%;
+ }
+
+ > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+ width: 60%;
+ }
+ }
+
+ &.oo-ui-fieldLayout-align-inline {
+ &.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding: 0.5em;
+ padding-left: 1em;
+ }
+
+ > .oo-ui-fieldLayout-body > .oo-ui-fieldLayout-field {
+ padding: 0.5em 0;
+ }
+ }
+
+ &.oo-ui-fieldLayout-align-top {
+ &.oo-ui-labelElement > .oo-ui-fieldLayout-body > .oo-ui-labelElement-label {
+ padding: 0.5em 0;
+ }
+ }
+
+ > .oo-ui-popupButtonWidget {
+ .oo-ui-inline-spacing(0);
+ margin-top: 0.25em;
+ }
+
+ &-disabled .oo-ui-labelElement-label {
+ color: #ccc;
+ }
+}
+
+.theme-oo-ui-actionFieldLayout () {}
+
+.theme-oo-ui-fieldsetLayout () {
+ margin: 0;
+ padding: 0;
+ border: none;
+
+ + .oo-ui-fieldsetLayout,
+ + .oo-ui-formLayout {
+ margin-top: 2em;
+ }
+
+ > .oo-ui-labelElement-label {
+ font-size: 1.1em;
+ margin-bottom: 0.5em;
+ padding: 0.25em 0;
+ font-weight: bold;
+ }
+
+ &.oo-ui-iconElement > .oo-ui-labelElement-label {
+ padding-left: 2em;
+ line-height: 1.8em;
+ }
+
+ &.oo-ui-iconElement > .oo-ui-iconElement-icon {
+ left: 0;
+ top: 0.25em;
+ width: @icon-size;
+ height: @icon-size;
+ }
+
+ > .oo-ui-popupButtonWidget {
+ .oo-ui-inline-spacing(0);
+ }
+}
+
+.theme-oo-ui-formLayout () {
+ + .oo-ui-fieldsetLayout,
+ + .oo-ui-formLayout {
+ margin-top: 2em;
+ }
+}
+
+.theme-oo-ui-menuLayout () {}
+
+.theme-oo-ui-panelLayout () {
+ &-padded {
+ padding: 1.25em;
+ }
+
+ &-framed {
+ border: 1px solid #aaa;
+ border-radius: 0.2em;
+ box-shadow: inset 0 -0.2em 0 0 rgba(0,0,0,0.2);
+ }
+}
+
+.theme-oo-ui-pageLayout () {}
+
+.theme-oo-ui-stackLayout () {}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/textures.json b/vendor/oojs/oojs-ui/src/themes/mediawiki/textures.json
new file mode 100644
index 00000000..e90730ab
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/textures.json
@@ -0,0 +1,8 @@
+{
+ "prefix": "oo-ui-texture",
+ "intro": "@import '../../../../src/styles/common';",
+ "images": {
+ "pending": { "file": "images/textures/pending.gif" },
+ "transparency": { "file": "images/textures/transparency.svg" }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/tools.less b/vendor/oojs/oojs-ui/src/themes/mediawiki/tools.less
new file mode 100644
index 00000000..fa5d6ddc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/tools.less
@@ -0,0 +1,422 @@
+@import 'common';
+
+@oo-ui-toolbar-normal: #fff;
+@oo-ui-toolbar-normal-hover: #eee;
+@oo-ui-toolbar-active: #e5e5e5;
+@oo-ui-toolbar-active-hover: #eee;
+@oo-ui-toolbar-pressed: #e7e7e7;
+@oo-ui-toolbar-bar-text: #555;
+@oo-ui-toolbar-dropdown-text: #000;
+
+.theme-oo-ui-toolbar () {
+ &-bar {
+ border-bottom: 1px solid #ccc;
+ background-color: @oo-ui-toolbar-normal;
+ box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.1);
+ font-weight: 500;
+ color: @oo-ui-toolbar-bar-text;
+
+ .oo-ui-toolbar-bar {
+ border: none;
+ background: none;
+ box-shadow: none;
+ }
+ }
+
+ &-actions {
+ > .oo-ui-buttonElement {
+ margin-top: 0.25em;
+ margin-bottom: 0.25em;
+ }
+
+ > .oo-ui-toolbar,
+ > .oo-ui-buttonElement:last-child {
+ margin-right: 0.5em;
+ }
+ }
+}
+
+.theme-oo-ui-tool () {}
+
+.theme-oo-ui-popupTool () {
+ .oo-ui-popupWidget {
+ /* @noflip */
+ margin-left: 1.25em;
+ }
+}
+
+.theme-oo-ui-toolGroupTool () {
+ > .oo-ui-popupToolGroup {
+ border: 0;
+ border-radius: 0;
+ margin: 0;
+ }
+
+ > .oo-ui-toolGroup {
+ border-right: none;
+ }
+
+ > .oo-ui-popupToolGroup > .oo-ui-popupToolGroup-handle {
+ height: 2.5em;
+ padding: 0.3125em;
+
+ .oo-ui-iconElement-icon {
+ height: 2.5em;
+ width: 1.875em;
+ }
+ }
+
+ > .oo-ui-popupToolGroup.oo-ui-labelElement > .oo-ui-popupToolGroup-handle {
+ .oo-ui-labelElement-label {
+ line-height: 2.1em;
+ }
+ }
+}
+
+.theme-oo-ui-toolGroup () {
+ border-radius: 0px;
+ border-right: 1px solid #ddd;
+
+ .oo-ui-toolbar-narrow & {
+ + .oo-ui-toolGroup {
+ margin-left: 0;
+ }
+ }
+
+ .oo-ui-toolGroup {
+ .oo-ui-widget-enabled {
+ border-right: none !important;
+ }
+ }
+}
+
+.theme-oo-ui-barToolGroup () {
+ > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ > .oo-ui-tool-link {
+ height: 1.875em;
+ padding: 0.625em;
+
+ .oo-ui-iconElement-icon {
+ height: 1.875em;
+ width: 1.875em;
+ }
+
+ .oo-ui-tool-title {
+ line-height: 2.1em; // 0.5em less
+ padding: 0em 0.4em;
+ }
+ }
+ }
+
+ &.oo-ui-widget-enabled > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ &.oo-ui-widget-enabled:hover {
+ border-color: rgba(0,0,0,0.2);
+ background-color: @oo-ui-toolbar-normal-hover;
+ }
+
+ > a.oo-ui-tool-link .oo-ui-tool-title {
+ color: @oo-ui-toolbar-bar-text;
+ }
+
+ &.oo-ui-tool-active {
+ &.oo-ui-widget-enabled {
+ border-color: rgba(0,0,0,0.2);
+ box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+ background-color: @oo-ui-toolbar-active;
+ }
+
+ &.oo-ui-widget-enabled:hover {
+ background-color: @oo-ui-toolbar-active-hover;
+ }
+
+ &.oo-ui-widget-enabled + .oo-ui-tool-active.oo-ui-widget-enabled {
+ border-left-color: rgba(0,0,0,0.1);
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-tool-link {
+ .oo-ui-tool-title {
+ color: #ccc;
+ }
+
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ > .oo-ui-tool-link .oo-ui-iconElement-icon {
+ opacity: 0.7;
+ }
+
+ &:hover > .oo-ui-tool-link .oo-ui-iconElement-icon {
+ opacity: 0.9;
+ }
+ }
+
+ &.oo-ui-widget-enabled:active {
+ background-color: @oo-ui-toolbar-pressed;
+ }
+ }
+
+ &.oo-ui-widget-disabled > .oo-ui-toolGroup-tools > .oo-ui-tool {
+ > a.oo-ui-tool-link {
+ .oo-ui-tool-title {
+ color: #ccc;
+ }
+
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+ }
+}
+
+.theme-oo-ui-popupToolGroup () {
+ height: 3.125em;
+ min-width: 2em;
+
+ .oo-ui-toolbar-narrow & {
+ min-width: 1.875em;
+ }
+
+ &.oo-ui-iconElement {
+ min-width: 3.125em;
+ .oo-ui-toolbar-narrow & {
+ min-width: 2.5em;
+ }
+ }
+
+ &.oo-ui-indicatorElement.oo-ui-iconElement {
+ min-width: 4.375em;
+
+ .oo-ui-toolbar-narrow & {
+ min-width: 3.75em;
+ }
+ }
+
+ &.oo-ui-labelElement {
+ .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+ line-height: 2.6em;
+ margin: 0 1em;
+
+ .oo-ui-toolbar-narrow & {
+ margin: 0 0.5em;
+ }
+ }
+
+ &.oo-ui-iconElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+ margin-left: 3em;
+
+ .oo-ui-toolbar-narrow & {
+ margin-left: 2.5em;
+ }
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-popupToolGroup-handle .oo-ui-labelElement-label {
+ margin-right: 2em;
+
+ .oo-ui-toolbar-narrow & {
+ margin-right: 1.75em;
+ }
+ }
+ }
+
+ &.oo-ui-widget-enabled &-handle:hover {
+ background-color: @oo-ui-toolbar-normal-hover;
+ }
+
+ &.oo-ui-widget-enabled &-handle:active {
+ background-color: @oo-ui-toolbar-active;
+ }
+
+ &-handle {
+ padding: 0.3125em;
+ height: 2.5em;
+
+ .oo-ui-indicatorElement-indicator {
+ width: 0.9375em;
+ height: 1.625em;
+ margin: 0.78125em 0.5em;
+ top: 0;
+ right: 0;
+ opacity: 0.3;
+
+ .oo-ui-toolbar-narrow & {
+ right: -0.3125em;
+ }
+ }
+
+ .oo-ui-iconElement-icon {
+ width: 1.875em;
+ height: 2.6em;
+ margin: 0.25em;
+ top: 0;
+ left: 0.3125em;
+ opacity: 0.7;
+
+ .oo-ui-toolbar-narrow & {
+ left: 0;
+ }
+ }
+ }
+
+ &-header {
+ line-height: 2.6em;
+ margin: 0 0.6em;
+ font-weight: bold;
+ }
+
+ &-active.oo-ui-widget-enabled {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+ background-color: @oo-ui-toolbar-normal-hover;
+ }
+
+ .oo-ui-toolGroup-tools {
+ top: 3.125em;
+ margin: 0 -1px;
+ border: 1px solid #ccc;
+ background-color: @oo-ui-toolbar-normal;
+ box-shadow: 0px 2px 3px rgba(0,0,0,0.2);
+ min-width: 16em;
+ }
+
+ .oo-ui-tool-link {
+ padding: 0.4em 0.625em;
+ box-sizing: border-box;
+
+ .oo-ui-iconElement-icon {
+ height: 2.5em;
+ width: 1.875em;
+ min-width: 1.875em;
+ }
+
+ .oo-ui-tool-title {
+ padding-left: 0.5em;
+ color: @oo-ui-toolbar-dropdown-text;
+ }
+
+ .oo-ui-tool-accel,
+ .oo-ui-tool-title {
+ line-height: 2em;
+ }
+
+ .oo-ui-tool-accel {
+ color: #888;
+ }
+ }
+}
+
+.theme-oo-ui-listToolGroup () {
+ &.oo-ui-popupToolGroup-active {
+ border-color: rgba(0,0,0,0.2);
+ }
+
+ .oo-ui-tool {
+ &.oo-ui-widget-enabled {
+ &:hover {
+ border-color: rgba(0,0,0,0.2);
+ background-color: @oo-ui-toolbar-normal-hover;
+ }
+
+ &:active {
+ background-color: @oo-ui-toolbar-pressed;
+ }
+
+ &:hover .oo-ui-tool-link .oo-ui-iconElement-icon {
+ opacity: 0.9;
+ }
+ }
+
+ &-active {
+ &.oo-ui-widget-enabled {
+ border-color: rgba(0,0,0,0.1);
+ box-shadow: inset 0 0.07em 0.07em 0 rgba(0, 0, 0, 0.07);
+ background-color: @oo-ui-toolbar-active;
+
+ + .oo-ui-tool-active.oo-ui-widget-enabled {
+ border-top-color: rgba(0,0,0,0.1);
+ }
+
+ &:hover {
+ border-color: rgba(0,0,0,0.2);
+ background-color: @oo-ui-toolbar-active-hover;
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-tool-link {
+ .oo-ui-tool-title {
+ color: #ccc;
+ }
+
+ .oo-ui-tool-accel {
+ color: #ddd;
+ }
+
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ color: #ccc;
+
+ .oo-ui-indicatorElement-indicator,
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+}
+
+.theme-oo-ui-menuToolGroup () {
+ .oo-ui-popupToolGroup-handle {
+ min-width: 10em;
+
+ .oo-ui-toolbar-narrow & {
+ min-width: 8.125em;
+ }
+ }
+
+ .oo-ui-tool {
+ &-link {
+ .oo-ui-iconElement-icon {
+ background-image: none;
+ }
+ }
+
+ &-active .oo-ui-tool-link .oo-ui-iconElement-icon {
+ .oo-ui-background-image-svg('@{oo-ui-default-image-path}/icons/check');
+ }
+
+ &.oo-ui-widget-enabled {
+ &:hover {
+ background-color: @oo-ui-toolbar-normal-hover;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-tool-link .oo-ui-tool-title {
+ color: #ccc;
+ }
+
+ .oo-ui-tool-link .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ color: #ccc;
+
+ .oo-ui-indicatorElement-indicator,
+ .oo-ui-iconElement-icon {
+ opacity: 0.2;
+ }
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/widgets.less b/vendor/oojs/oojs-ui/src/themes/mediawiki/widgets.less
new file mode 100644
index 00000000..2f072878
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/widgets.less
@@ -0,0 +1,978 @@
+@import 'common';
+
+.theme-oo-ui-widget () {}
+
+.theme-oo-ui-outlineControlsWidget () {
+ height: 3em;
+ background-color: #fff;
+
+ &-items,
+ &-movers {
+ height: 2em;
+ margin: 0.5em 0.5em 0.5em 0;
+ padding: 0;
+ }
+
+ > .oo-ui-iconElement-icon {
+ width: 1.5em;
+ height: 2em;
+ margin: 0.5em 0 0.5em 0.5em;
+ opacity: 0.2;
+ }
+}
+
+.theme-oo-ui-toggleWidget () {}
+
+.theme-oo-ui-buttonGroupWidget () {
+ display: inline-block;
+ white-space: nowrap;
+ border-radius: @border-radius;
+
+ .oo-ui-inline-spacing(0.5em);
+ .oo-ui-buttonElement {
+ .oo-ui-inline-spacing(0);
+ }
+
+ .oo-ui-buttonElement-framed {
+ .oo-ui-buttonElement-button {
+ border-radius: 0;
+ margin-left: -1px;
+ }
+
+ &:first-child .oo-ui-buttonElement-button {
+ border-bottom-left-radius: @border-radius;
+ border-top-left-radius: @border-radius;
+ margin-left: 0;
+ }
+
+ &:last-child .oo-ui-buttonElement-button {
+ border-bottom-right-radius: @border-radius;
+ border-top-right-radius: @border-radius;
+ }
+ }
+}
+
+.theme-oo-ui-buttonWidget () {
+ .oo-ui-inline-spacing(0.5em);
+}
+
+.theme-oo-ui-actionWidget () {
+ &.oo-ui-pendingElement-pending {
+ .oo-ui-background-image('@{oo-ui-default-image-path}/textures/pending.gif');
+ }
+}
+
+.theme-oo-ui-popupButtonWidget () {
+ &.oo-ui-buttonElement-frameless > .oo-ui-popupWidget {
+ // Compensate for icon being inset
+ /* @noflip */
+ left: 1em;
+ }
+
+ &.oo-ui-buttonElement-framed > .oo-ui-popupWidget {
+ // Compensate for icon being inset
+ /* @noflip */
+ left: 1.75em;
+ }
+}
+
+.theme-oo-ui-toggleButtonWidget () {
+ .oo-ui-inline-spacing(0.5em);
+}
+
+.theme-oo-ui-iconWidget () {
+ line-height: 2.5em;
+ height: @icon-size;
+ width: @icon-size;
+
+ &.oo-ui-widget-disabled {
+ opacity: 0.2;
+ }
+}
+
+.theme-oo-ui-indicatorWidget () {
+ line-height: 2.5em;
+ height: @indicator-size;
+ width: @indicator-size;
+ margin: @indicator-size / 2;
+
+ &.oo-ui-widget-disabled {
+ opacity: 0.2;
+ }
+}
+
+.theme-oo-ui-dropdownWidget () {
+ margin: 0.25em 0;
+ width: 100%;
+ max-width: 50em;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ &-handle {
+ height: 2.5em;
+ border: 1px solid #ccc;
+ border-radius: 0.1em;
+
+ .oo-ui-indicatorElement-indicator {
+ right: 0;
+ }
+
+ .oo-ui-iconElement-icon {
+ left: 0.25em;
+ }
+
+ .oo-ui-labelElement-label {
+ line-height: 2.5em;
+ margin: 0 1em;
+ }
+
+ .oo-ui-indicatorElement-indicator {
+ top: 0;
+ width: @indicator-size;
+ height: @indicator-size;
+ margin: 0.775em;
+ }
+
+ .oo-ui-iconElement-icon {
+ top: 0;
+ width: @icon-size;
+ height: @icon-size;
+ margin: 0.3em;
+ }
+ }
+
+ &:hover .oo-ui-dropdownWidget-handle {
+ border-color: #aaa;
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-dropdownWidget-handle {
+ color: #ccc;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #ddd;
+ background-color: #f3f3f3;
+ }
+ .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+ }
+ }
+
+ &.oo-ui-iconElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+ margin-left: 3em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-dropdownWidget-handle .oo-ui-labelElement-label {
+ margin-right: 2em;
+ }
+
+ .oo-ui-selectWidget {
+ border-top-color: #fff;
+ }
+}
+
+.theme-oo-ui-inputWidget () {
+ .oo-ui-inline-spacing(0.5em);
+}
+
+.theme-oo-ui-buttonInputWidget () {}
+
+.theme-oo-ui-checkboxInputWidget () {
+ position: relative;
+ line-height: @input-size;
+ // Prevent the fake span from jumping to the next line of text
+ white-space: nowrap;
+
+ * {
+ font: inherit;
+ vertical-align: middle;
+ }
+
+ // This input element is visually replaced by the the span that follows
+ input[type="checkbox"] {
+ // Use opacity so that VoiceOver software can still identify it
+ opacity: 0;
+ // Render "on top of" the span, so that it's still clickable
+ z-index: 1;
+ position: relative;
+
+ // Having margin would offset the input from where the span is absolutely positioned,
+ // making only the overlap region receive events
+ margin: 0;
+
+ // Ensure the invisible input takes up the required width
+ width: @input-size;
+ height: @input-size;
+
+ // Needed for Firefox mobile (See bug 71750 to workaround default Firefox stylesheet)
+ max-width: none;
+
+ & + span {
+ cursor: pointer;
+ .oo-ui-transition(background-size @medium-ease-out-back);
+ .oo-ui-box-sizing( border-box );
+ position: absolute;
+ left: 0;
+ border-radius: @border-radius;
+ width: @input-size;
+ height: @input-size;
+ background-color: white;
+ border: 1px solid @input-border-color;
+ .oo-ui-background-image-svg('@{oo-ui-default-image-path}/icons/check-constructive');
+ .oo-ui-background-image-safari('@{oo-ui-default-image-path}/icons/check-constructive');
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-origin: border-box;
+ background-size: 0 0;
+ }
+
+ &:checked + span {
+ background-size: 100% 100%;
+ }
+
+ &:active + span {
+ background-color: @input-active-color;
+ border-color: @input-active-color;
+ }
+
+ &:focus + span {
+ border-width: @input-focus-border-width;
+ }
+
+ &:focus:hover + span,
+ &:hover + span {
+ border-bottom-width: @input-hover-border-bottom-width;
+ }
+
+ &:disabled + span {
+ cursor: default;
+ background-color: @input-disabled-color;
+ border-color: @input-disabled-color;
+ }
+
+ &:disabled:checked + span {
+ .oo-ui-background-image-svg('@{oo-ui-default-image-path}/icons/check-invert');
+ .oo-ui-background-image-safari('@{oo-ui-default-image-path}/icons/check-invert');
+ }
+ }
+}
+
+.theme-oo-ui-dropdownInputWidget () {
+ width: 100%;
+ max-width: 50em;
+
+ select {
+ height: 2.5em;
+ padding: 0.5em;
+ font-size: inherit;
+ font-family: inherit;
+ .oo-ui-box-sizing(border-box);
+ border: 1px solid #ccc;
+ }
+
+ &.oo-ui-widget-enabled {
+ select:hover,
+ select:focus {
+ border-color: #aaa;
+ outline: none;
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ select {
+ color: #ccc;
+ border-color: #ddd;
+ background-color: #f3f3f3;
+ }
+ }
+}
+
+.theme-oo-ui-radioInputWidget () {
+ position: relative;
+ line-height: @input-size;
+ // Prevent the fake span from jumping to the next line of text
+ white-space: nowrap;
+
+ * {
+ font: inherit;
+ vertical-align: middle;
+ }
+
+ // This input element is visually replaced by the the span that follows
+ input[type="radio"] {
+ // Use opacity so that VoiceOver software can still identify it
+ opacity: 0;
+ // Render "on top of" the span, so that it's still clickable
+ z-index: 1;
+ position: relative;
+
+ // Having margin would offset the input from where the span is absolutely positioned,
+ // making only the overlap region receive events
+ margin: 0;
+
+ // Ensure the invisible input takes up the required width
+ width: @input-size;
+ height: @input-size;
+
+ // Needed for Firefox mobile (See bug 71750 to workaround default Firefox stylesheet)
+ max-width: none;
+
+ & + span {
+ cursor: pointer;
+ .oo-ui-transition(background-size @medium-ease-out-back);
+ .oo-ui-box-sizing(border-box);
+ position: absolute;
+ left: 0;
+ border-radius: 100%;
+ width: @input-size;
+ height: @input-size;
+ background: white;
+ border: 1px solid @input-border-color;
+ .oo-ui-background-image-svg('@{oo-ui-default-image-path}/icons/circle-constructive');
+ .oo-ui-background-image-safari('@{oo-ui-default-image-path}/icons/circle-constructive');
+ background-repeat: no-repeat;
+ background-position: center center;
+ background-origin: border-box;
+ background-size: 0 0;
+ }
+
+ &:checked + span {
+ background-size: 100% 100%;
+ }
+
+ &:active + span {
+ background-color: @input-active-color;
+ border-color: @input-active-color;
+ }
+
+ &:focus + span {
+ border-width: @input-focus-border-width;
+ }
+
+ &:focus:hover + span,
+ &:hover + span {
+ border-bottom-width: @input-hover-border-bottom-width;
+ }
+
+ &:disabled + span {
+ cursor: default;
+ background-color: @input-disabled-color;
+ border-color: @input-disabled-color;
+ }
+
+ &:disabled:checked + span {
+ .oo-ui-background-image-svg('@{oo-ui-default-image-path}/icons/circle-invert');
+ .oo-ui-background-image-safari('@{oo-ui-default-image-path}/icons/circle-invert');
+ }
+ }
+}
+
+.theme-oo-ui-textInputWidget () {
+ width: 100%;
+ max-width: 50em;
+
+ input,
+ textarea {
+ padding: 0.5em;
+ margin: 0;
+ font-size: inherit;
+ font-family: inherit;
+ background-color: #fff;
+ color: black;
+ border: solid 1px #ccc;
+ box-shadow: inset 0 0 0 0 @progressive;
+ border-radius: 0.1em;
+ .oo-ui-transition(box-shadow @quick-ease);
+ .oo-ui-box-sizing(border-box);
+ }
+
+ &-decorated {
+ input,
+ textarea {
+ padding-left: 2em;
+ }
+ }
+
+ &-icon {
+ width: 2em;
+ }
+
+ &.oo-ui-widget-enabled {
+ input,
+ textarea {
+ .oo-ui-transition(
+ border @medium-ease-out-sine,
+ box-shadow @medium-ease-out-sine
+ );
+ }
+
+ input:focus,
+ textarea:focus {
+ outline: none;
+ border-color: @progressive;
+ box-shadow: inset 0 0 0 0.1em @progressive;
+ }
+
+ input[readonly],
+ textarea[readonly] {
+ color: #777;
+ text-shadow: 0 1px 1px #fff;
+
+ &:focus {
+ border-color: #ccc;
+ box-shadow: inset 0 0 0 0.1em #ccc;
+ }
+ }
+
+ &.oo-ui-flaggedElement-invalid {
+ input,
+ textarea {
+ border-color: red;
+ box-shadow: inset 0 0 0 0 red;
+ }
+
+ input:focus,
+ textarea:focus {
+ border-color: red;
+ box-shadow: inset 0 0 0 0.1em red;
+ }
+ }
+ }
+
+ &.oo-ui-widget-disabled {
+ input,
+ textarea {
+ color: #ccc;
+ text-shadow: 0 1px 1px #fff;
+ border-color: #ddd;
+ background-color: #f3f3f3;
+ }
+ .oo-ui-iconElement-icon,
+ .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+ }
+ .oo-ui-labelElement-label {
+ color: #ddd;
+ text-shadow: 0 1px 1px #fff;
+ }
+ }
+
+ &.oo-ui-pendingElement-pending {
+ input,
+ textarea {
+ background-color: transparent;
+ .oo-ui-background-image('@{oo-ui-default-image-path}/textures/pending.gif');
+ }
+ }
+
+ &.oo-ui-iconElement {
+ input,
+ textarea {
+ padding-left: 2.75em;
+ }
+
+ .oo-ui-iconElement-icon {
+ left: 0.4em;
+ width: @icon-size;
+ margin-left: 0.1em;
+ height: 100%;
+ background-position: right center;
+ }
+ }
+
+ &.oo-ui-indicatorElement {
+ input,
+ textarea {
+ padding-right: @icon-size;
+ }
+
+ .oo-ui-indicatorElement-indicator {
+ width: @indicator-size;
+ margin: 0 0.775em;
+ height: 100%;
+ }
+ }
+
+ > .oo-ui-labelElement-label {
+ padding: 0.4em;
+ line-height: 1.5em;
+ color: #888;
+ }
+
+ &-labelPosition-after {
+ &.oo-ui-indicatorElement > .oo-ui-labelElement-label {
+ margin-right: 2em;
+ }
+ }
+
+ &-labelPosition-before {
+ &.oo-ui-iconElement > .oo-ui-labelElement-label {
+ margin-left: 2.5em;
+ }
+ }
+}
+
+.theme-oo-ui-comboBoxWidget () {
+ width: 100%;
+ max-width: 50em;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ .oo-ui-textInputWidget {
+ input,
+ textarea {
+ height: 2.35em;
+ }
+ }
+}
+
+.theme-oo-ui-labelWidget () {}
+
+.theme-oo-ui-optionWidget () {
+ padding: 0.25em 0.5em;
+ border: none;
+
+ &-highlighted {
+ background-color: #eee;
+ }
+
+ .oo-ui-labelElement-label {
+ line-height: 1.5em;
+ }
+
+ .oo-ui-selectWidget-depressed &-selected,
+ .oo-ui-selectWidget-pressed &-pressed,
+ .oo-ui-selectWidget-pressed &-pressed&-highlighted,
+ .oo-ui-selectWidget-pressed &-pressed&-highlighted&-selected {
+ background-color: @pressed-color;
+ }
+
+ &.oo-ui-widget-disabled {
+ color: #ccc;
+ }
+}
+
+.theme-oo-ui-decoratedOptionWidget () {
+ padding: 0.5em 2em 0.5em 3em;
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon,
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ top: 0;
+ height: 100%;
+ }
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon {
+ width: @icon-size;
+ left: 0.5em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ width: @indicator-size;
+ right: 0.5em;
+ }
+
+ &.oo-ui-widget-disabled {
+ .oo-ui-iconElement-icon,
+ .oo-ui-indicatorElement-indicator {
+ opacity: 0.2;
+ }
+ }
+}
+
+.theme-oo-ui-buttonOptionWidget () {
+ padding: 0;
+ background-color: transparent;
+
+ .oo-ui-buttonElement-button {
+ height: @icon-size;
+ }
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon {
+ margin-top: 0;
+ }
+
+ &.oo-ui-optionWidget-selected,
+ &.oo-ui-optionWidget-pressed,
+ &.oo-ui-optionWidget-highlighted {
+ background-color: transparent;
+ }
+
+ // Override DecoratedOptionWidget styles
+ &.oo-ui-widget-disabled {
+ .oo-ui-iconElement-icon,
+ .oo-ui-indicatorElement-indicator {
+ opacity: 1;
+ }
+ }
+}
+
+.theme-oo-ui-radioOptionWidget () {
+ padding: 0.25em 0;
+ background-color: transparent;
+
+ &.oo-ui-optionWidget-selected,
+ &.oo-ui-optionWidget-pressed,
+ &.oo-ui-optionWidget-highlighted {
+ background-color: transparent;
+ }
+
+ &.oo-ui-labelElement .oo-ui-labelElement-label {
+ padding: 0.25em;
+ padding-left: 1em;
+ }
+
+ .oo-ui-radioInputWidget {
+ margin-right: 0;
+ }
+}
+
+.theme-oo-ui-menuOptionWidget () {
+ padding: 0.5em 1em;
+
+ &.oo-ui-optionWidget {
+ &-selected {
+ background-color: @select;
+ color: rgba(0,0,0,0.8);
+
+ .oo-ui-iconElement-icon {
+ display: none;
+ }
+ }
+ &-highlighted {
+ background-color: #eee;
+ color: black;
+ }
+ &-selected&-highlighted {
+ background-color: @select;
+ }
+ }
+}
+
+.theme-oo-ui-menuSectionOptionWidget () {
+ padding: 0.33em 0.75em;
+ color: #888;
+}
+
+.theme-oo-ui-outlineOptionWidget () {
+ font-size: 1.1em;
+ padding: 0.75em;
+
+ &.oo-ui-indicatorElement .oo-ui-labelElement-label {
+ padding-right: 1.5em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ opacity: 0.5;
+ }
+
+ &-level-0 {
+ padding-left: 3.5em;
+
+ .oo-ui-iconElement-icon {
+ left: 1em;
+ }
+ }
+ &-level-1 {
+ padding-left: 5em;
+
+ .oo-ui-iconElement-icon {
+ left: 2.5em;
+ }
+ }
+
+ &-level-2 {
+ padding-left: 6.5em;
+
+ .oo-ui-iconElement-icon {
+ left: 4em;
+ }
+ }
+
+ .oo-ui-selectWidget-depressed &.oo-ui-optionWidget-selected {
+ background-color: @pressed-color;
+ text-shadow: 0 1px 1px #fff;
+ }
+
+ &.oo-ui-flaggedElement-important {
+ font-weight: bold;
+ }
+
+ &.oo-ui-flaggedElement-placeholder {
+ font-style: italic;
+ }
+
+ &.oo-ui-flaggedElement-empty {
+ .oo-ui-iconElement-icon {
+ opacity: 0.5;
+ }
+ .oo-ui-labelElement-label {
+ color: #777;
+ }
+ }
+}
+
+.theme-oo-ui-tabOptionWidget () {
+ padding: 0.35em 1em;
+ margin: 0.5em 0 0 0.75em;
+ border: 1px solid transparent;
+ border-bottom: none;
+ border-top-left-radius: @border-radius;
+ border-top-right-radius: @border-radius;
+ color: #666;
+ font-weight: bold;
+
+ &.oo-ui-widget-enabled {
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.3);
+ }
+
+ &:active {
+ background-color: rgba(255, 255, 255, 0.8);
+ }
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-labelElement-label {
+ padding-right: 1.5em;
+ }
+
+ &.oo-ui-indicatorElement .oo-ui-indicatorElement-indicator {
+ opacity: 0.5;
+ }
+
+ .oo-ui-selectWidget-pressed &.oo-ui-optionWidget-selected,
+ .oo-ui-selectWidget-depressed &.oo-ui-optionWidget-selected,
+ &.oo-ui-optionWidget-selected:hover {
+ background-color: #fff;
+ color: #333;
+ }
+}
+
+.theme-oo-ui-popupWidget () {
+ &-popup {
+ border: 1px solid #aaa;
+ border-radius: 0.2em;
+ background-color: #fff;
+ box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2);
+ }
+
+ @anchor-size: 9px;
+
+ &-anchored {
+ .oo-ui-popupWidget-popup {
+ margin-top: @anchor-size;
+ }
+
+ .oo-ui-popupWidget-anchor:before,
+ .oo-ui-popupWidget-anchor:after {
+ content: "";
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-style: solid;
+ border-color: transparent;
+ border-top: 0;
+ }
+
+ .oo-ui-popupWidget-anchor:before {
+ bottom: -@anchor-size - 1px;
+ left: -@anchor-size;
+ border-bottom-color: #888;
+ border-width: @anchor-size + 1px;
+ }
+
+ .oo-ui-popupWidget-anchor:after {
+ bottom: -@anchor-size - 1px;
+ left: -@anchor-size + 1px;
+ border-bottom-color: #fff;
+ border-width: @anchor-size;
+ }
+ }
+
+ &-transitioning .oo-ui-popupWidget-popup {
+ .oo-ui-transition(width @quick-ease, height @quick-ease, left @quick-ease);
+ }
+
+ &-head {
+ height: 2.5em;
+
+ .oo-ui-buttonWidget {
+ margin: 0.25em;
+ }
+
+ .oo-ui-labelElement-label {
+ margin: 0.75em 1em;
+ }
+ }
+
+ &-body-padded {
+ padding: 0 1em;
+ }
+}
+
+.theme-oo-ui-searchWidget () {
+ &-query {
+ height: 4em;
+ padding: 0 1em;
+ border-bottom: 1px solid #ccc;
+
+ .oo-ui-textInputWidget {
+ margin: 0.75em 0;
+ }
+ }
+
+ &-results {
+ top: 4em;
+ padding: 1em;
+ line-height: 0;
+ }
+}
+
+.theme-oo-ui-selectWidget () {}
+
+.theme-oo-ui-buttonSelectWidget () {
+ border-radius: @border-radius;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ .oo-ui-buttonOptionWidget {
+ .oo-ui-buttonElement-button {
+ border-radius: 0;
+ margin-left: -1px;
+ }
+
+ &:first-child .oo-ui-buttonElement-button {
+ border-bottom-left-radius: @border-radius;
+ border-top-left-radius: @border-radius;
+ margin-left: 0;
+ }
+
+ &:last-child .oo-ui-buttonElement-button {
+ border-bottom-right-radius: @border-radius;
+ border-top-right-radius: @border-radius;
+ }
+ }
+}
+
+.theme-oo-ui-radioSelectWidget () {}
+
+.theme-oo-ui-menuSelectWidget () {
+ background: #fff;
+ margin-top: -1px;
+ border: 1px solid #aaa;
+ border-radius: 0 0 0.2em 0.2em;
+ padding-bottom: 0.25em;
+ box-shadow: inset 0 -0.2em 0 0 rgba(0, 0, 0, 0.2), 0 0.1em 0 0 rgba(0, 0, 0, 0.2);
+}
+
+.theme-oo-ui-textInputMenuSelectWidget () {}
+
+.theme-oo-ui-outlineSelectWidget () {}
+
+.theme-oo-ui-tabSelectWidget () {
+ background-color: #ddd;
+}
+
+.theme-oo-ui-toggleSwitchWidget () {
+ @travelDistance: 2em;
+ height: 2em;
+ width: @travelDistance + 2em;
+ border-radius: 1em;
+ border: 1px #ddd solid;
+
+ .oo-ui-inline-spacing(0.5em);
+
+ &-grip {
+ top: 0.25em;
+ left: 0.25em;
+ width: 1.5em;
+ height: 1.5em;
+ margin-top: -1px;
+ border-radius: 1em;
+ border: 1px #ddd solid;
+ background-color: #f7f7f7;
+
+ .oo-ui-transition(left @quick-ease, margin-left @quick-ease);
+ }
+
+ &-glow {
+ border-radius: 1em;
+ background-color: #f7f7f7;
+
+ .oo-ui-transition(background-color @quick-ease);
+ }
+
+ &.oo-ui-toggleWidget-on {
+ .oo-ui-toggleSwitchWidget-grip {
+ left: @travelDistance + 0.25em;
+ margin-left: -2px;
+ }
+ }
+
+ &.oo-ui-toggleWidget-off {
+ .oo-ui-toggleSwitchWidget-glow {
+ display: block;
+ }
+ .oo-ui-toggleSwitchWidget-grip {
+ left: 0.25em;
+ margin-left: 0;
+ }
+ }
+
+ &.oo-ui-widget-enabled {
+ border: 1px #ccc solid;
+
+ &:hover {
+ border-color: #aaa;
+ }
+
+ .oo-ui-toggleSwitchWidget-grip {
+ background-color: #fff;
+ border-color: #aaa;
+ }
+
+ &.oo-ui-toggleWidget-on {
+ .oo-ui-toggleSwitchWidget-glow {
+ background-color: @pressed-color;
+ }
+ }
+
+ &.oo-ui-toggleWidget-off {
+ .oo-ui-toggleSwitchWidget-glow {
+ background-color: #fff;
+ }
+ }
+ }
+}
+
+.theme-oo-ui-progressBarWidget () {
+ max-width: 50em;
+ border: 1px solid #ccc;
+ border-radius: 0.1em;
+ overflow: hidden;
+
+ &-bar {
+ height: 1em;
+ background: #ddd;
+ .oo-ui-transition(width 200ms, margin-left 200ms);
+ }
+ &-indeterminate {
+ .oo-ui-progressBarWidget-bar {
+ .oo-ui-animation(oo-ui-progressBarWidget-slide 2s infinite linear);
+ width: 40%;
+ margin-left: -10%;
+ border-left-width: 1px;
+ }
+ }
+ &.oo-ui-widget-disabled {
+ opacity: 0.6;
+ }
+}
+
+.oo-ui-progressBarWidget-slide-frames () {
+ from { margin-left: -40%; }
+ to { margin-left: 100%; }
+}
+@-webkit-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@-moz-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@-ms-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@-o-keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
+@keyframes oo-ui-progressBarWidget-slide { .oo-ui-progressBarWidget-slide-frames }
diff --git a/vendor/oojs/oojs-ui/src/themes/mediawiki/windows.less b/vendor/oojs/oojs-ui/src/themes/mediawiki/windows.less
new file mode 100644
index 00000000..423233ce
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/themes/mediawiki/windows.less
@@ -0,0 +1,300 @@
+@import 'common';
+
+.theme-oo-ui-window () {
+ background: transparent;
+}
+
+.theme-oo-ui-dialog () {
+ &-content > .oo-ui-window-body {
+ outline: 1px solid #aaa;
+ }
+}
+
+.theme-oo-ui-messageDialog () {
+ &-title,
+ &-message {
+ display: block;
+ text-align: center;
+ padding-top: 0.5em;
+ }
+
+ &-title {
+ font-size: 1.5em;
+ line-height: 1em;
+ color: #000;
+ }
+
+ &-message {
+ font-size: 0.9em;
+ line-height: 1.25em;
+ color: #666;
+
+ &-verbose {
+ font-size: 1.1em;
+ line-height: 1.5em;
+ text-align: left;
+ }
+ }
+
+ &-actions {
+ &-horizontal {
+ .oo-ui-actionWidget {
+ border-right: 1px solid #e5e5e5;
+
+ &:last-child {
+ border-right-width: 0;
+ }
+ }
+ }
+
+ &-vertical {
+ .oo-ui-actionWidget {
+ border-bottom: 1px solid #e5e5e5;
+
+ &:last-child {
+ border-bottom-width: 0;
+ }
+ }
+ }
+
+ .oo-ui-actionWidget {
+ height: 3.4em;
+
+ &.oo-ui-labelElement .oo-ui-labelElement-label {
+ text-align: center;
+ line-height: 3.4em;
+ padding: 0 2em;
+ }
+
+ &:hover {
+ background-color: rgba(0,0,0,0.05);
+ }
+
+ &:active {
+ background-color: rgba(0,0,0,0.1);
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive {
+ &:hover {
+ background-color: rgba(8,126,204,0.05);
+ }
+
+ &:active {
+ background-color: rgba(8,126,204,0.1);
+ }
+
+ .oo-ui-labelElement-label {
+ font-weight: bold;
+ }
+ }
+
+ &-constructive {
+ &:hover {
+ background-color: rgba(118,171,54,0.05);
+ }
+
+ &:active {
+ background-color: rgba(118,171,54,0.1);
+ }
+ }
+
+ &-destructive {
+ &:hover {
+ background-color: rgba(212,83,83,0.05);
+ }
+
+ &:active {
+ background-color: rgba(212,83,83,0.1);
+ }
+ }
+ }
+ }
+ }
+}
+
+.theme-oo-ui-processDialog () {
+ &-content {
+ .oo-ui-window-head {
+ height: 3.4em;
+
+ &.oo-ui-pendingElement-pending {
+ .oo-ui-background-image('@{oo-ui-default-image-path}/textures/pending.gif');
+ }
+ }
+
+ .oo-ui-window-body {
+ top: 3.4em;
+ outline: 1px solid rgba(0,0,0,0.2);
+ }
+ }
+
+ &-navigation {
+ position: relative;
+ height: 3.4em;
+ padding: 0 1em;
+ }
+
+ &-location {
+ padding: 0.75em 0;
+ height: @icon-size;
+ cursor: default;
+ text-align: center;
+ }
+
+ &-title {
+ font-weight: bold;
+ line-height: @icon-size;
+ }
+
+ &-actions {
+ &-safe,
+ &-primary,
+ &-other {
+ .oo-ui-actionWidget {
+ .oo-ui-buttonElement-button {
+ min-width: @icon-size;
+ min-height: @icon-size;
+ }
+
+ .oo-ui-labelElement-label {
+ line-height: @icon-size;
+ }
+
+ &.oo-ui-iconElement .oo-ui-iconElement-icon {
+ margin-top: -0.125em;
+ }
+
+ &.oo-ui-buttonElement-framed {
+ margin: 0.75em 0 0.75em 0.75em;
+ .oo-ui-buttonElement-button {
+ padding: 0 1em;
+ vertical-align: middle;
+ }
+ }
+ }
+ }
+
+ &-safe,
+ &-primary {
+ .oo-ui-actionWidget {
+ &:hover {
+ background-color: rgba(0,0,0,0.05);
+ }
+
+ &:active {
+ background-color: rgba(0,0,0,0.1);
+ }
+
+ &.oo-ui-buttonElement-framed {
+ margin: 0.75em;
+ .oo-ui-buttonElement-button {
+ /* Adjust for border so text aligns with title */
+ margin: -1px;
+ }
+ }
+
+ &.oo-ui-flaggedElement {
+ &-progressive {
+ &:hover {
+ background-color: rgba(8,126,204,0.05);
+ }
+
+ &:active {
+ background-color: rgba(8,126,204,0.1);
+ }
+
+ .oo-ui-labelElement-label {
+ font-weight: bold;
+ }
+ }
+
+ &-constructive {
+ &:hover {
+ background-color: rgba(118,171,54,0.05);
+ }
+
+ &:active {
+ background-color: rgba(118,171,54,0.1);
+ }
+ }
+
+ &-destructive {
+ &:hover {
+ background-color: rgba(212,83,83,0.05);
+ }
+
+ &:active {
+ background-color: rgba(212,83,83,0.1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ > .oo-ui-window-frame {
+ min-height: 5em;
+ }
+
+ &-errors {
+ background-color: rgba(255,255,255,0.9);
+ padding: 3em 3em 1.5em 3em;
+ text-align: center;
+
+ .oo-ui-buttonWidget {
+ margin: 2em 1em 2em 1em;
+ }
+
+ &-title {
+ font-size: 1.5em;
+ color: #000;
+ margin-bottom: 2em;
+ }
+ }
+
+ &-error {
+ text-align: left;
+ margin: 1em;
+ padding: 1em;
+ border: 1px solid #ff9e9e;
+ background-color: #fff7f7;
+ border-radius: 0.25em;
+ }
+}
+
+.theme-oo-ui-windowManager () {
+ &-modal > .oo-ui-dialog {
+ background-color: rgba(255,255,255,0.5);
+ opacity: 0;
+
+ .oo-ui-transition(opacity 250ms ease-in-out);
+
+ > .oo-ui-window-frame {
+ top: 1em;
+ bottom: 1em;
+ background-color: #fff;
+
+ opacity: 0;
+ .oo-ui-transform(scale(0.5));
+ .oo-ui-transition(all 250ms ease-in-out);
+ }
+
+ &.oo-ui-window-ready {
+ /* Fade window overlay */
+ opacity: 1;
+
+ > .oo-ui-window-frame {
+ /* Fade frame */
+ opacity: 1;
+ .oo-ui-transform(scale(1));
+ }
+ }
+ }
+
+ &-modal&-floating > .oo-ui-dialog > .oo-ui-window-frame {
+ border: 1px solid #aaa;
+ border-radius: 0.2em;
+ box-shadow: inset 0 -0.2em 0 0 rgba(0,0,0,0.2);
+ }
+}
diff --git a/vendor/oojs/oojs-ui/src/toolgroups/BarToolGroup.js b/vendor/oojs/oojs-ui/src/toolgroups/BarToolGroup.js
new file mode 100644
index 00000000..038894d6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/toolgroups/BarToolGroup.js
@@ -0,0 +1,35 @@
+/**
+ * Horizontal bar layout of tools as icon buttons.
+ *
+ * @class
+ * @extends OO.ui.ToolGroup
+ *
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.BarToolGroup = function OoUiBarToolGroup( toolbar, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+ config = toolbar;
+ toolbar = config.toolbar;
+ }
+
+ // Parent constructor
+ OO.ui.BarToolGroup.super.call( this, toolbar, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-barToolGroup' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.BarToolGroup, OO.ui.ToolGroup );
+
+/* Static Properties */
+
+OO.ui.BarToolGroup.static.titleTooltips = true;
+
+OO.ui.BarToolGroup.static.accelTooltips = true;
+
+OO.ui.BarToolGroup.static.name = 'bar';
diff --git a/vendor/oojs/oojs-ui/src/toolgroups/ListToolGroup.js b/vendor/oojs/oojs-ui/src/toolgroups/ListToolGroup.js
new file mode 100644
index 00000000..e78f507e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/toolgroups/ListToolGroup.js
@@ -0,0 +1,133 @@
+/**
+ * Drop down list layout of tools as labeled icon buttons.
+ *
+ * This layout allows some tools to be collapsible, controlled by a "More" / "Fewer" option at the
+ * bottom of the main list. These are not automatically positioned at the bottom of the list; you
+ * may want to use the 'promote' and 'demote' configuration options to achieve this.
+ *
+ * @class
+ * @extends OO.ui.PopupToolGroup
+ *
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
+ * @cfg {Array} [allowCollapse] List of tools that can be collapsed. Remaining tools will be always
+ * shown.
+ * @cfg {Array} [forceExpand] List of tools that *may not* be collapsed. All remaining tools will be
+ * allowed to be collapsed.
+ * @cfg {boolean} [expanded=false] Whether the collapsible tools are expanded by default
+ */
+OO.ui.ListToolGroup = function OoUiListToolGroup( toolbar, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+ config = toolbar;
+ toolbar = config.toolbar;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Properties (must be set before parent constructor, which calls #populate)
+ this.allowCollapse = config.allowCollapse;
+ this.forceExpand = config.forceExpand;
+ this.expanded = config.expanded !== undefined ? config.expanded : false;
+ this.collapsibleTools = [];
+
+ // Parent constructor
+ OO.ui.ListToolGroup.super.call( this, toolbar, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-listToolGroup' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ListToolGroup, OO.ui.PopupToolGroup );
+
+/* Static Properties */
+
+OO.ui.ListToolGroup.static.name = 'list';
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ListToolGroup.prototype.populate = function () {
+ var i, len, allowCollapse = [];
+
+ OO.ui.ListToolGroup.super.prototype.populate.call( this );
+
+ // Update the list of collapsible tools
+ if ( this.allowCollapse !== undefined ) {
+ allowCollapse = this.allowCollapse;
+ } else if ( this.forceExpand !== undefined ) {
+ allowCollapse = OO.simpleArrayDifference( Object.keys( this.tools ), this.forceExpand );
+ }
+
+ this.collapsibleTools = [];
+ for ( i = 0, len = allowCollapse.length; i < len; i++ ) {
+ if ( this.tools[ allowCollapse[ i ] ] !== undefined ) {
+ this.collapsibleTools.push( this.tools[ allowCollapse[ i ] ] );
+ }
+ }
+
+ // Keep at the end, even when tools are added
+ this.$group.append( this.getExpandCollapseTool().$element );
+
+ this.getExpandCollapseTool().toggle( this.collapsibleTools.length !== 0 );
+ this.updateCollapsibleState();
+};
+
+OO.ui.ListToolGroup.prototype.getExpandCollapseTool = function () {
+ if ( this.expandCollapseTool === undefined ) {
+ var ExpandCollapseTool = function () {
+ ExpandCollapseTool.super.apply( this, arguments );
+ };
+
+ OO.inheritClass( ExpandCollapseTool, OO.ui.Tool );
+
+ ExpandCollapseTool.prototype.onSelect = function () {
+ this.toolGroup.expanded = !this.toolGroup.expanded;
+ this.toolGroup.updateCollapsibleState();
+ this.setActive( false );
+ };
+ ExpandCollapseTool.prototype.onUpdateState = function () {
+ // Do nothing. Tool interface requires an implementation of this function.
+ };
+
+ ExpandCollapseTool.static.name = 'more-fewer';
+
+ this.expandCollapseTool = new ExpandCollapseTool( this );
+ }
+ return this.expandCollapseTool;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ListToolGroup.prototype.onMouseKeyUp = function ( e ) {
+ // Do not close the popup when the user wants to show more/fewer tools
+ if (
+ $( e.target ).closest( '.oo-ui-tool-name-more-fewer' ).length &&
+ ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+ ) {
+ // HACK: Prevent the popup list from being hidden. Skip the PopupToolGroup implementation (which
+ // hides the popup list when a tool is selected) and call ToolGroup's implementation directly.
+ return OO.ui.ListToolGroup.super.super.prototype.onMouseKeyUp.call( this, e );
+ } else {
+ return OO.ui.ListToolGroup.super.prototype.onMouseKeyUp.call( this, e );
+ }
+};
+
+OO.ui.ListToolGroup.prototype.updateCollapsibleState = function () {
+ var i, len;
+
+ this.getExpandCollapseTool()
+ .setIcon( this.expanded ? 'collapse' : 'expand' )
+ .setTitle( OO.ui.msg( this.expanded ? 'ooui-toolgroup-collapse' : 'ooui-toolgroup-expand' ) );
+
+ for ( i = 0, len = this.collapsibleTools.length; i < len; i++ ) {
+ this.collapsibleTools[ i ].toggle( this.expanded );
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/toolgroups/MenuToolGroup.js b/vendor/oojs/oojs-ui/src/toolgroups/MenuToolGroup.js
new file mode 100644
index 00000000..aef69a93
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/toolgroups/MenuToolGroup.js
@@ -0,0 +1,58 @@
+/**
+ * Drop down menu layout of tools as selectable menu items.
+ *
+ * @class
+ * @extends OO.ui.PopupToolGroup
+ *
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.MenuToolGroup = function OoUiMenuToolGroup( toolbar, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+ config = toolbar;
+ toolbar = config.toolbar;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.MenuToolGroup.super.call( this, toolbar, config );
+
+ // Events
+ this.toolbar.connect( this, { updateState: 'onUpdateState' } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-menuToolGroup' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MenuToolGroup, OO.ui.PopupToolGroup );
+
+/* Static Properties */
+
+OO.ui.MenuToolGroup.static.name = 'menu';
+
+/* Methods */
+
+/**
+ * Handle the toolbar state being updated.
+ *
+ * When the state changes, the title of each active item in the menu will be joined together and
+ * used as a label for the group. The label will be empty if none of the items are active.
+ */
+OO.ui.MenuToolGroup.prototype.onUpdateState = function () {
+ var name,
+ labelTexts = [];
+
+ for ( name in this.tools ) {
+ if ( this.tools[ name ].isActive() ) {
+ labelTexts.push( this.tools[ name ].getTitle() );
+ }
+ }
+
+ this.setLabel( labelTexts.join( ', ' ) || ' ' );
+};
diff --git a/vendor/oojs/oojs-ui/src/toolgroups/PopupToolGroup.js b/vendor/oojs/oojs-ui/src/toolgroups/PopupToolGroup.js
new file mode 100644
index 00000000..0fbdf4fc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/toolgroups/PopupToolGroup.js
@@ -0,0 +1,187 @@
+/**
+ * Popup list of tools with an icon and optional label.
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.ToolGroup
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.TitledElement
+ * @mixins OO.ui.ClippableElement
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {OO.ui.Toolbar} toolbar
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [header] Text to display at the top of the pop-up
+ */
+OO.ui.PopupToolGroup = function OoUiPopupToolGroup( toolbar, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolbar ) && config === undefined ) {
+ config = toolbar;
+ toolbar = config.toolbar;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.PopupToolGroup.super.call( this, toolbar, config );
+
+ // Properties
+ this.active = false;
+ this.dragging = false;
+ this.onBlurHandler = this.onBlur.bind( this );
+ this.$handle = $( '<span>' );
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, config );
+ OO.ui.IndicatorElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.TitledElement.call( this, config );
+ OO.ui.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
+
+ // Events
+ this.$handle.on( {
+ keydown: this.onHandleMouseKeyDown.bind( this ),
+ keyup: this.onHandleMouseKeyUp.bind( this ),
+ mousedown: this.onHandleMouseKeyDown.bind( this ),
+ mouseup: this.onHandleMouseKeyUp.bind( this )
+ } );
+
+ // Initialization
+ this.$handle
+ .addClass( 'oo-ui-popupToolGroup-handle' )
+ .append( this.$icon, this.$label, this.$indicator );
+ // If the pop-up should have a header, add it to the top of the toolGroup.
+ // Note: If this feature is useful for other widgets, we could abstract it into an
+ // OO.ui.HeaderedElement mixin constructor.
+ if ( config.header !== undefined ) {
+ this.$group
+ .prepend( $( '<span>' )
+ .addClass( 'oo-ui-popupToolGroup-header' )
+ .text( config.header )
+ );
+ }
+ this.$element
+ .addClass( 'oo-ui-popupToolGroup' )
+ .prepend( this.$handle );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PopupToolGroup, OO.ui.ToolGroup );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.IconElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.TitledElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.ClippableElement );
+OO.mixinClass( OO.ui.PopupToolGroup, OO.ui.TabIndexedElement );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.PopupToolGroup.prototype.setDisabled = function () {
+ // Parent method
+ OO.ui.PopupToolGroup.super.prototype.setDisabled.apply( this, arguments );
+
+ if ( this.isDisabled() && this.isElementAttached() ) {
+ this.setActive( false );
+ }
+};
+
+/**
+ * Handle focus being lost.
+ *
+ * The event is actually generated from a mouseup/keyup, so it is not a normal blur event object.
+ *
+ * @param {jQuery.Event} e Mouse up or key up event
+ */
+OO.ui.PopupToolGroup.prototype.onBlur = function ( e ) {
+ // Only deactivate when clicking outside the dropdown element
+ if ( $( e.target ).closest( '.oo-ui-popupToolGroup' )[ 0 ] !== this.$element[ 0 ] ) {
+ this.setActive( false );
+ }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.PopupToolGroup.prototype.onMouseKeyUp = function ( e ) {
+ // Only close toolgroup when a tool was actually selected
+ if (
+ !this.isDisabled() && this.pressed && this.pressed === this.getTargetTool( e ) &&
+ ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+ ) {
+ this.setActive( false );
+ }
+ return OO.ui.PopupToolGroup.super.prototype.onMouseKeyUp.call( this, e );
+};
+
+/**
+ * Handle mouse up and key up events.
+ *
+ * @param {jQuery.Event} e Mouse up or key up event
+ */
+OO.ui.PopupToolGroup.prototype.onHandleMouseKeyUp = function ( e ) {
+ if (
+ !this.isDisabled() &&
+ ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+ ) {
+ return false;
+ }
+};
+
+/**
+ * Handle mouse down and key down events.
+ *
+ * @param {jQuery.Event} e Mouse down or key down event
+ */
+OO.ui.PopupToolGroup.prototype.onHandleMouseKeyDown = function ( e ) {
+ if (
+ !this.isDisabled() &&
+ ( e.which === 1 || e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER )
+ ) {
+ this.setActive( !this.active );
+ return false;
+ }
+};
+
+/**
+ * Switch into active mode.
+ *
+ * When active, mouseup events anywhere in the document will trigger deactivation.
+ */
+OO.ui.PopupToolGroup.prototype.setActive = function ( value ) {
+ value = !!value;
+ if ( this.active !== value ) {
+ this.active = value;
+ if ( value ) {
+ this.getElementDocument().addEventListener( 'mouseup', this.onBlurHandler, true );
+ this.getElementDocument().addEventListener( 'keyup', this.onBlurHandler, true );
+
+ // Try anchoring the popup to the left first
+ this.$element.addClass( 'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left' );
+ this.toggleClipping( true );
+ if ( this.isClippedHorizontally() ) {
+ // Anchoring to the left caused the popup to clip, so anchor it to the right instead
+ this.toggleClipping( false );
+ this.$element
+ .removeClass( 'oo-ui-popupToolGroup-left' )
+ .addClass( 'oo-ui-popupToolGroup-right' );
+ this.toggleClipping( true );
+ }
+ } else {
+ this.getElementDocument().removeEventListener( 'mouseup', this.onBlurHandler, true );
+ this.getElementDocument().removeEventListener( 'keyup', this.onBlurHandler, true );
+ this.$element.removeClass(
+ 'oo-ui-popupToolGroup-active oo-ui-popupToolGroup-left oo-ui-popupToolGroup-right'
+ );
+ this.toggleClipping( false );
+ }
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/tools/PopupTool.js b/vendor/oojs/oojs-ui/src/tools/PopupTool.js
new file mode 100644
index 00000000..98f93d75
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/tools/PopupTool.js
@@ -0,0 +1,59 @@
+/**
+ * Tool that shows a popup when selected.
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Tool
+ * @mixins OO.ui.PopupElement
+ *
+ * @constructor
+ * @param {OO.ui.ToolGroup} toolGroup
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.PopupTool = function OoUiPopupTool( toolGroup, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
+ config = toolGroup;
+ toolGroup = config.toolGroup;
+ }
+
+ // Parent constructor
+ OO.ui.PopupTool.super.call( this, toolGroup, config );
+
+ // Mixin constructors
+ OO.ui.PopupElement.call( this, config );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-popupTool' )
+ .append( this.popup.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PopupTool, OO.ui.Tool );
+OO.mixinClass( OO.ui.PopupTool, OO.ui.PopupElement );
+
+/* Methods */
+
+/**
+ * Handle the tool being selected.
+ *
+ * @inheritdoc
+ */
+OO.ui.PopupTool.prototype.onSelect = function () {
+ if ( !this.isDisabled() ) {
+ this.popup.toggle();
+ }
+ this.setActive( false );
+ return false;
+};
+
+/**
+ * Handle the toolbar state being updated.
+ *
+ * @inheritdoc
+ */
+OO.ui.PopupTool.prototype.onUpdateState = function () {
+ this.setActive( false );
+};
diff --git a/vendor/oojs/oojs-ui/src/tools/ToolGroupTool.js b/vendor/oojs/oojs-ui/src/tools/ToolGroupTool.js
new file mode 100644
index 00000000..b8f70a3a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/tools/ToolGroupTool.js
@@ -0,0 +1,96 @@
+/**
+ * Tool that has a tool group inside. This is a bad workaround for the lack of proper hierarchical
+ * menus in toolbars (T74159).
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Tool
+ *
+ * @constructor
+ * @param {OO.ui.ToolGroup} toolGroup
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.ToolGroupTool = function OoUiToolGroupTool( toolGroup, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( toolGroup ) && config === undefined ) {
+ config = toolGroup;
+ toolGroup = config.toolGroup;
+ }
+
+ // Parent constructor
+ OO.ui.ToolGroupTool.super.call( this, toolGroup, config );
+
+ // Properties
+ this.innerToolGroup = this.createGroup( this.constructor.static.groupConfig );
+
+ // Events
+ this.innerToolGroup.connect( this, { disable: 'onToolGroupDisable' } );
+
+ // Initialization
+ this.$link.remove();
+ this.$element
+ .addClass( 'oo-ui-toolGroupTool' )
+ .append( this.innerToolGroup.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ToolGroupTool, OO.ui.Tool );
+
+/* Static Properties */
+
+/**
+ * Tool group configuration. See OO.ui.Toolbar#setup for the accepted values.
+ *
+ * @property {Object.<string,Array>}
+ */
+OO.ui.ToolGroupTool.static.groupConfig = {};
+
+/* Methods */
+
+/**
+ * Handle the tool being selected.
+ *
+ * @inheritdoc
+ */
+OO.ui.ToolGroupTool.prototype.onSelect = function () {
+ this.innerToolGroup.setActive( !this.innerToolGroup.active );
+ return false;
+};
+
+/**
+ * Synchronize disabledness state of the tool with the inner toolgroup.
+ *
+ * @private
+ * @param {boolean} disabled Element is disabled
+ */
+OO.ui.ToolGroupTool.prototype.onToolGroupDisable = function ( disabled ) {
+ this.setDisabled( disabled );
+};
+
+/**
+ * Handle the toolbar state being updated.
+ *
+ * @inheritdoc
+ */
+OO.ui.ToolGroupTool.prototype.onUpdateState = function () {
+ this.setActive( false );
+};
+
+/**
+ * Build a OO.ui.ToolGroup from the configuration.
+ *
+ * @param {Object.<string,Array>} group Tool group configuration. See OO.ui.Toolbar#setup for the
+ * accepted values.
+ * @return {OO.ui.ListToolGroup}
+ */
+OO.ui.ToolGroupTool.prototype.createGroup = function ( group ) {
+ if ( group.include === '*' ) {
+ // Apply defaults to catch-all groups
+ if ( group.label === undefined ) {
+ group.label = OO.ui.msg( 'ooui-toolbar-more' );
+ }
+ }
+
+ return this.toolbar.getToolGroupFactory().create( 'list', this.toolbar, group );
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ActionWidget.js b/vendor/oojs/oojs-ui/src/widgets/ActionWidget.js
new file mode 100644
index 00000000..789f04f1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ActionWidget.js
@@ -0,0 +1,170 @@
+/**
+ * An ActionWidget is a {@link OO.ui.ButtonWidget button widget} that executes an action.
+ * Action widgets are used with OO.ui.ActionSet, which manages the behavior and availability
+ * of the actions.
+ *
+ * Both actions and action sets are primarily used with {@link OO.ui.Dialog Dialogs}.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information
+ * and examples.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Windows/Process_Dialogs#Action_sets
+ *
+ * @class
+ * @extends OO.ui.ButtonWidget
+ * @mixins OO.ui.PendingElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [action] Symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
+ * @cfg {string[]} [modes] Symbolic names of the modes (e.g., ‘edit’ or ‘read’) in which the action
+ * should be made available. See the action set's {@link OO.ui.ActionSet#setMode setMode} method
+ * for more information about setting modes.
+ * @cfg {boolean} [framed=false] Render the action button with a frame
+ */
+OO.ui.ActionWidget = function OoUiActionWidget( config ) {
+ // Configuration initialization
+ config = $.extend( { framed: false }, config );
+
+ // Parent constructor
+ OO.ui.ActionWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.PendingElement.call( this, config );
+
+ // Properties
+ this.action = config.action || '';
+ this.modes = config.modes || [];
+ this.width = 0;
+ this.height = 0;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-actionWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ActionWidget, OO.ui.ButtonWidget );
+OO.mixinClass( OO.ui.ActionWidget, OO.ui.PendingElement );
+
+/* Events */
+
+/**
+ * A resize event is emitted when the size of the widget changes.
+ *
+ * @event resize
+ */
+
+/* Methods */
+
+/**
+ * Check if the action is configured to be available in the specified `mode`.
+ *
+ * @param {string} mode Name of mode
+ * @return {boolean} The action is configured with the mode
+ */
+OO.ui.ActionWidget.prototype.hasMode = function ( mode ) {
+ return this.modes.indexOf( mode ) !== -1;
+};
+
+/**
+ * Get the symbolic name of the action (e.g., ‘continue’ or ‘cancel’).
+ *
+ * @return {string}
+ */
+OO.ui.ActionWidget.prototype.getAction = function () {
+ return this.action;
+};
+
+/**
+ * Get the symbolic name of the mode or modes for which the action is configured to be available.
+ *
+ * The current mode is set with the action set's {@link OO.ui.ActionSet#setMode setMode} method.
+ * Only actions that are configured to be avaiable in the current mode will be visible. All other actions
+ * are hidden.
+ *
+ * @return {string[]}
+ */
+OO.ui.ActionWidget.prototype.getModes = function () {
+ return this.modes.slice();
+};
+
+/**
+ * Emit a resize event if the size has changed.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.ActionWidget.prototype.propagateResize = function () {
+ var width, height;
+
+ if ( this.isElementAttached() ) {
+ width = this.$element.width();
+ height = this.$element.height();
+
+ if ( width !== this.width || height !== this.height ) {
+ this.width = width;
+ this.height = height;
+ this.emit( 'resize' );
+ }
+ }
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ActionWidget.prototype.setIcon = function () {
+ // Mixin method
+ OO.ui.IconElement.prototype.setIcon.apply( this, arguments );
+ this.propagateResize();
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ActionWidget.prototype.setLabel = function () {
+ // Mixin method
+ OO.ui.LabelElement.prototype.setLabel.apply( this, arguments );
+ this.propagateResize();
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ActionWidget.prototype.setFlags = function () {
+ // Mixin method
+ OO.ui.FlaggedElement.prototype.setFlags.apply( this, arguments );
+ this.propagateResize();
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ActionWidget.prototype.clearFlags = function () {
+ // Mixin method
+ OO.ui.FlaggedElement.prototype.clearFlags.apply( this, arguments );
+ this.propagateResize();
+
+ return this;
+};
+
+/**
+ * Toggle the visibility of the action button.
+ *
+ * @param {boolean} [show] Show button, omit to toggle visibility
+ * @chainable
+ */
+OO.ui.ActionWidget.prototype.toggle = function () {
+ // Parent method
+ OO.ui.ActionWidget.super.prototype.toggle.apply( this, arguments );
+ this.propagateResize();
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ButtonGroupWidget.js b/vendor/oojs/oojs-ui/src/widgets/ButtonGroupWidget.js
new file mode 100644
index 00000000..f1388ab8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ButtonGroupWidget.js
@@ -0,0 +1,53 @@
+/**
+ * A ButtonGroupWidget groups related buttons and is used together with OO.ui.ButtonWidget and
+ * its subclasses. Each button in a group is addressed by a unique reference. Buttons can be added,
+ * removed, and cleared from the group.
+ *
+ * @example
+ * // Example: A ButtonGroupWidget with two buttons
+ * var button1 = new OO.ui.PopupButtonWidget( {
+ * label: 'Select a category',
+ * icon: 'menu',
+ * popup: {
+ * $content: $( '<p>List of categories...</p>' ),
+ * padded: true,
+ * align: 'left'
+ * }
+ * } );
+ * var button2 = new OO.ui.ButtonWidget( {
+ * label: 'Add item'
+ * });
+ * var buttonGroup = new OO.ui.ButtonGroupWidget( {
+ * items: [button1, button2]
+ * } );
+ * $( 'body' ).append( buttonGroup.$element );
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.ButtonWidget[]} [items] Buttons to add
+ */
+OO.ui.ButtonGroupWidget = function OoUiButtonGroupWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ButtonGroupWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.GroupElement.call( this, $.extend( {}, config, { $group: this.$element } ) );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-buttonGroupWidget' );
+ if ( Array.isArray( config.items ) ) {
+ this.addItems( config.items );
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ButtonGroupWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.ButtonGroupWidget, OO.ui.GroupElement );
diff --git a/vendor/oojs/oojs-ui/src/widgets/ButtonInputWidget.js b/vendor/oojs/oojs-ui/src/widgets/ButtonInputWidget.js
new file mode 100644
index 00000000..1d4d97fe
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ButtonInputWidget.js
@@ -0,0 +1,121 @@
+/**
+ * ButtonInputWidget is used to submit HTML forms and is intended to be used within
+ * a OO.ui.FormLayout. If you do not need the button to work with HTML forms, you probably
+ * want to use OO.ui.ButtonWidget instead. Button input widgets can be rendered as either an
+ * HTML `<button/>` (the default) or an HTML `<input/>` tags. See the
+ * [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * @example
+ * // A ButtonInputWidget rendered as an HTML button, the default.
+ * var button = new OO.ui.ButtonInputWidget( {
+ * label: 'Input button',
+ * icon: 'check',
+ * value: 'check'
+ * } );
+ * $( 'body' ).append( button.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs#Button_inputs
+ *
+ * @class
+ * @extends OO.ui.InputWidget
+ * @mixins OO.ui.ButtonElement
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.TitledElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [type='button'] The value of the HTML `'type'` attribute: 'button', 'submit' or 'reset'.
+ * @cfg {boolean} [useInputTag=false] Use an `<input/>` tag instead of a `<button/>` tag, the default.
+ * Widgets configured to be an `<input/>` do not support {@link #icon icons} and {@link #indicator indicators},
+ * non-plaintext {@link #label labels}, or {@link #value values}. In general, useInputTag should only
+ * be set to `true` when there’s need to support IE6 in a form with multiple buttons.
+ */
+OO.ui.ButtonInputWidget = function OoUiButtonInputWidget( config ) {
+ // Configuration initialization
+ config = $.extend( { type: 'button', useInputTag: false }, config );
+
+ // Properties (must be set before parent constructor, which calls #setValue)
+ this.useInputTag = config.useInputTag;
+
+ // Parent constructor
+ OO.ui.ButtonInputWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.ButtonElement.call( this, $.extend( {}, config, { $button: this.$input } ) );
+ OO.ui.IconElement.call( this, config );
+ OO.ui.IndicatorElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$input } ) );
+
+ // Initialization
+ if ( !config.useInputTag ) {
+ this.$input.append( this.$icon, this.$label, this.$indicator );
+ }
+ this.$element.addClass( 'oo-ui-buttonInputWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ButtonInputWidget, OO.ui.InputWidget );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.ButtonElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.IconElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.ButtonInputWidget, OO.ui.TitledElement );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ * @private
+ */
+OO.ui.ButtonInputWidget.prototype.getInputElement = function ( config ) {
+ var html = '<' + ( config.useInputTag ? 'input' : 'button' ) + ' type="' + config.type + '">';
+ return $( html );
+};
+
+/**
+ * Set label value.
+ *
+ * If #useInputTag is `true`, the label is set as the `value` of the `<input/>` tag.
+ *
+ * @param {jQuery|string|Function|null} label Label nodes, text, a function that returns nodes or
+ * text, or `null` for no label
+ * @chainable
+ */
+OO.ui.ButtonInputWidget.prototype.setLabel = function ( label ) {
+ OO.ui.LabelElement.prototype.setLabel.call( this, label );
+
+ if ( this.useInputTag ) {
+ if ( typeof label === 'function' ) {
+ label = OO.ui.resolveMsg( label );
+ }
+ if ( label instanceof jQuery ) {
+ label = label.text();
+ }
+ if ( !label ) {
+ label = '';
+ }
+ this.$input.val( label );
+ }
+
+ return this;
+};
+
+/**
+ * Set the value of the input.
+ *
+ * This method is disabled for button inputs configured as {@link #useInputTag <input/> tags}, as
+ * they do not support {@link #value values}.
+ *
+ * @param {string} value New value
+ * @chainable
+ */
+OO.ui.ButtonInputWidget.prototype.setValue = function ( value ) {
+ if ( !this.useInputTag ) {
+ OO.ui.ButtonInputWidget.super.prototype.setValue.call( this, value );
+ }
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ButtonOptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/ButtonOptionWidget.js
new file mode 100644
index 00000000..7758c949
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ButtonOptionWidget.js
@@ -0,0 +1,60 @@
+/**
+ * ButtonOptionWidget is a special type of {@link OO.ui.ButtonElement button element} that
+ * can be selected and configured with data. The class is
+ * used with OO.ui.ButtonSelectWidget to create a selection of button options. Please see the
+ * [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Button_selects_and_options
+ *
+ * @class
+ * @extends OO.ui.DecoratedOptionWidget
+ * @mixins OO.ui.ButtonElement
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.ButtonOptionWidget = function OoUiButtonOptionWidget( config ) {
+ // Configuration initialization
+ config = $.extend( { tabIndex: -1 }, config );
+
+ // Parent constructor
+ OO.ui.ButtonOptionWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.ButtonElement.call( this, config );
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-buttonOptionWidget' );
+ this.$button.append( this.$element.contents() );
+ this.$element.append( this.$button );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ButtonOptionWidget, OO.ui.DecoratedOptionWidget );
+OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.ButtonElement );
+OO.mixinClass( OO.ui.ButtonOptionWidget, OO.ui.TabIndexedElement );
+
+/* Static Properties */
+
+// Allow button mouse down events to pass through so they can be handled by the parent select widget
+OO.ui.ButtonOptionWidget.static.cancelButtonMouseDownEvents = false;
+
+OO.ui.ButtonOptionWidget.static.highlightable = false;
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ButtonOptionWidget.prototype.setSelected = function ( state ) {
+ OO.ui.ButtonOptionWidget.super.prototype.setSelected.call( this, state );
+
+ if ( this.constructor.static.selectable ) {
+ this.setActive( state );
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ButtonSelectWidget.js b/vendor/oojs/oojs-ui/src/widgets/ButtonSelectWidget.js
new file mode 100644
index 00000000..18177761
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ButtonSelectWidget.js
@@ -0,0 +1,62 @@
+/**
+ * ButtonSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains
+ * button options and is used together with
+ * OO.ui.ButtonOptionWidget. The ButtonSelectWidget provides an interface for
+ * highlighting, choosing, and selecting mutually exclusive options. Please see
+ * the [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * @example
+ * // Example: A ButtonSelectWidget that contains three ButtonOptionWidgets
+ * var option1 = new OO.ui.ButtonOptionWidget( {
+ * data: 1,
+ * label: 'Option 1',
+ * title: 'Button option 1'
+ * } );
+ *
+ * var option2 = new OO.ui.ButtonOptionWidget( {
+ * data: 2,
+ * label: 'Option 2',
+ * title: 'Button option 2'
+ * } );
+ *
+ * var option3 = new OO.ui.ButtonOptionWidget( {
+ * data: 3,
+ * label: 'Option 3',
+ * title: 'Button option 3'
+ * } );
+ *
+ * var buttonSelect=new OO.ui.ButtonSelectWidget( {
+ * items: [ option1, option2, option3 ]
+ * } );
+ * $( 'body' ).append( buttonSelect.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ *
+ * @class
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.ButtonSelectWidget = function OoUiButtonSelectWidget( config ) {
+ // Parent constructor
+ OO.ui.ButtonSelectWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.TabIndexedElement.call( this, config );
+
+ // Events
+ this.$element.on( {
+ focus: this.bindKeyDownListener.bind( this ),
+ blur: this.unbindKeyDownListener.bind( this )
+ } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-buttonSelectWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ButtonSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.ButtonSelectWidget, OO.ui.TabIndexedElement );
diff --git a/vendor/oojs/oojs-ui/src/widgets/ButtonWidget.js b/vendor/oojs/oojs-ui/src/widgets/ButtonWidget.js
new file mode 100644
index 00000000..474fafed
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ButtonWidget.js
@@ -0,0 +1,215 @@
+/**
+ * ButtonWidget is a generic widget for buttons. A wide variety of looks,
+ * feels, and functionality can be customized via the class’s configuration options
+ * and methods. Please see the [OOjs UI documentation on MediaWiki] [1] for more information
+ * and examples.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches
+ *
+ * @example
+ * // A button widget
+ * var button = new OO.ui.ButtonWidget( {
+ * label: 'Button with Icon',
+ * icon: 'remove',
+ * iconTitle: 'Remove'
+ * } );
+ * $( 'body' ).append( button.$element );
+ *
+ * NOTE: HTML form buttons should use the OO.ui.ButtonInputWidget class.
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.ButtonElement
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.TitledElement
+ * @mixins OO.ui.FlaggedElement
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [href] Hyperlink to visit when the button is clicked.
+ * @cfg {string} [target] The frame or window in which to open the hyperlink.
+ * @cfg {boolean} [noFollow] Search engine traversal hint (default: true)
+ */
+OO.ui.ButtonWidget = function OoUiButtonWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ButtonWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.ButtonElement.call( this, config );
+ OO.ui.IconElement.call( this, config );
+ OO.ui.IndicatorElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
+ OO.ui.FlaggedElement.call( this, config );
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) );
+
+ // Properties
+ this.href = null;
+ this.target = null;
+ this.noFollow = false;
+
+ // Events
+ this.connect( this, { disable: 'onDisable' } );
+
+ // Initialization
+ this.$button.append( this.$icon, this.$label, this.$indicator );
+ this.$element
+ .addClass( 'oo-ui-buttonWidget' )
+ .append( this.$button );
+ this.setHref( config.href );
+ this.setTarget( config.target );
+ this.setNoFollow( config.noFollow );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ButtonWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.ButtonElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.IconElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.TitledElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.FlaggedElement );
+OO.mixinClass( OO.ui.ButtonWidget, OO.ui.TabIndexedElement );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ButtonWidget.prototype.onMouseDown = function ( e ) {
+ if ( !this.isDisabled() ) {
+ // Remove the tab-index while the button is down to prevent the button from stealing focus
+ this.$button.removeAttr( 'tabindex' );
+ }
+
+ return OO.ui.ButtonElement.prototype.onMouseDown.call( this, e );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ButtonWidget.prototype.onMouseUp = function ( e ) {
+ if ( !this.isDisabled() ) {
+ // Restore the tab-index after the button is up to restore the button's accessibility
+ this.$button.attr( 'tabindex', this.tabIndex );
+ }
+
+ return OO.ui.ButtonElement.prototype.onMouseUp.call( this, e );
+};
+
+/**
+ * Get hyperlink location.
+ *
+ * @return {string} Hyperlink location
+ */
+OO.ui.ButtonWidget.prototype.getHref = function () {
+ return this.href;
+};
+
+/**
+ * Get hyperlink target.
+ *
+ * @return {string} Hyperlink target
+ */
+OO.ui.ButtonWidget.prototype.getTarget = function () {
+ return this.target;
+};
+
+/**
+ * Get search engine traversal hint.
+ *
+ * @return {boolean} Whether search engines should avoid traversing this hyperlink
+ */
+OO.ui.ButtonWidget.prototype.getNoFollow = function () {
+ return this.noFollow;
+};
+
+/**
+ * Set hyperlink location.
+ *
+ * @param {string|null} href Hyperlink location, null to remove
+ */
+OO.ui.ButtonWidget.prototype.setHref = function ( href ) {
+ href = typeof href === 'string' ? href : null;
+
+ if ( href !== this.href ) {
+ this.href = href;
+ this.updateHref();
+ }
+
+ return this;
+};
+
+/**
+ * Update the `href` attribute, in case of changes to href or
+ * disabled state.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.ButtonWidget.prototype.updateHref = function () {
+ if ( this.href !== null && !this.isDisabled() ) {
+ this.$button.attr( 'href', this.href );
+ } else {
+ this.$button.removeAttr( 'href' );
+ }
+
+ return this;
+};
+
+/**
+ * Handle disable events.
+ *
+ * @private
+ * @param {boolean} disabled Element is disabled
+ */
+OO.ui.ButtonWidget.prototype.onDisable = function () {
+ this.updateHref();
+};
+
+/**
+ * Set hyperlink target.
+ *
+ * @param {string|null} target Hyperlink target, null to remove
+ */
+OO.ui.ButtonWidget.prototype.setTarget = function ( target ) {
+ target = typeof target === 'string' ? target : null;
+
+ if ( target !== this.target ) {
+ this.target = target;
+ if ( target !== null ) {
+ this.$button.attr( 'target', target );
+ } else {
+ this.$button.removeAttr( 'target' );
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Set search engine traversal hint.
+ *
+ * @param {boolean} noFollow True if search engines should avoid traversing this hyperlink
+ */
+OO.ui.ButtonWidget.prototype.setNoFollow = function ( noFollow ) {
+ noFollow = typeof noFollow === 'boolean' ? noFollow : true;
+
+ if ( noFollow !== this.noFollow ) {
+ this.noFollow = noFollow;
+ if ( noFollow ) {
+ this.$button.attr( 'rel', 'nofollow' );
+ } else {
+ this.$button.removeAttr( 'rel' );
+ }
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/CheckboxInputWidget.js b/vendor/oojs/oojs-ui/src/widgets/CheckboxInputWidget.js
new file mode 100644
index 00000000..1ac93bc5
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/CheckboxInputWidget.js
@@ -0,0 +1,110 @@
+/**
+ * CheckboxInputWidgets, like HTML checkboxes, can be selected and/or configured with a value.
+ * Note that these {@link OO.ui.InputWidget input widgets} are best laid out
+ * in {@link OO.ui.FieldLayout field layouts} that use the {@link OO.ui.FieldLayout#align inline}
+ * alignment. For more information, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
+ *
+ * @example
+ * // An example of selected, unselected, and disabled checkbox inputs
+ * var checkbox1=new OO.ui.CheckboxInputWidget( {
+ * value: 'a',
+ * selected: true
+ * } );
+ * var checkbox2=new OO.ui.CheckboxInputWidget( {
+ * value: 'b'
+ * } );
+ * var checkbox3=new OO.ui.CheckboxInputWidget( {
+ * value:'c',
+ * disabled: true
+ * } );
+ * // Create a fieldset layout with fields for each checkbox.
+ * var fieldset = new OO.ui.FieldsetLayout( {
+ * label: 'Checkboxes'
+ * } );
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( checkbox1, { label: 'Selected checkbox', align: 'inline' } ),
+ * new OO.ui.FieldLayout( checkbox2, { label: 'Unselected checkbox', align: 'inline' } ),
+ * new OO.ui.FieldLayout( checkbox3, { label: 'Disabled checkbox', align: 'inline' } ),
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @class
+ * @extends OO.ui.InputWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [selected=false] Select the checkbox initially. By default, the checkbox is not selected.
+ */
+OO.ui.CheckboxInputWidget = function OoUiCheckboxInputWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.CheckboxInputWidget.super.call( this, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-checkboxInputWidget' );
+ this.setSelected( config.selected !== undefined ? config.selected : false );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.CheckboxInputWidget, OO.ui.InputWidget );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ * @private
+ */
+OO.ui.CheckboxInputWidget.prototype.getInputElement = function () {
+ return $( '<input type="checkbox" />' );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.CheckboxInputWidget.prototype.onEdit = function () {
+ var widget = this;
+ if ( !this.isDisabled() ) {
+ // Allow the stack to clear so the value will be updated
+ setTimeout( function () {
+ widget.setSelected( widget.$input.prop( 'checked' ) );
+ } );
+ }
+};
+
+/**
+ * Set selection state of this checkbox.
+ *
+ * @param {boolean} state `true` for selected
+ * @chainable
+ */
+OO.ui.CheckboxInputWidget.prototype.setSelected = function ( state ) {
+ state = !!state;
+ if ( this.selected !== state ) {
+ this.selected = state;
+ this.$input.prop( 'checked', this.selected );
+ this.emit( 'change', this.selected );
+ }
+ return this;
+};
+
+/**
+ * Check if this checkbox is selected.
+ *
+ * @return {boolean} Checkbox is selected
+ */
+OO.ui.CheckboxInputWidget.prototype.isSelected = function () {
+ // Resynchronize our internal data with DOM data. Other scripts executing on the page can modify
+ // it, and we won't know unless they're kind enough to trigger a 'change' event.
+ var selected = this.$input.prop( 'checked' );
+ if ( this.selected !== selected ) {
+ this.setSelected( selected );
+ }
+ return this.selected;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ComboBoxWidget.js b/vendor/oojs/oojs-ui/src/widgets/ComboBoxWidget.js
new file mode 100644
index 00000000..346d17ee
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ComboBoxWidget.js
@@ -0,0 +1,230 @@
+/**
+ * ComboBoxWidgets combine a {@link OO.ui.TextInputWidget text input} (where a value
+ * can be entered manually) and a {@link OO.ui.MenuSelectWidget menu of options} (from which
+ * a value can be chosen instead). Users can choose options from the combo box in one of two ways:
+ *
+ * - by typing a value in the text input field. If the value exactly matches the value of a menu
+ * option, that option will appear to be selected.
+ * - by choosing a value from the menu. The value of the chosen option will then appear in the text
+ * input field.
+ *
+ * For more information about menus and options, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * @example
+ * // Example: A ComboBoxWidget.
+ * var comboBox = new OO.ui.ComboBoxWidget( {
+ * label: 'ComboBoxWidget',
+ * input: { value: 'Option One' },
+ * menu: {
+ * items: [
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'Option 1',
+ * label: 'Option One'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'Option 2',
+ * label: 'Option Two'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'Option 3',
+ * label: 'Option Three'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'Option 4',
+ * label: 'Option Four'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'Option 5',
+ * label: 'Option Five'
+ * } )
+ * ]
+ * }
+ * } );
+ * $( 'body' ).append( comboBox.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [menu] Configuration options to pass to the {@link OO.ui.MenuSelectWidget menu select widget}.
+ * @cfg {Object} [input] Configuration options to pass to the {@link OO.ui.TextInputWidget text input widget}.
+ * @cfg {jQuery} [$overlay] Render the menu into a separate layer. This configuration is useful in cases where
+ * the expanded menu is larger than its containing `<div>`. The specified overlay layer is usually on top of the
+ * containing `<div>` and has a larger area. By default, the menu uses relative positioning.
+ */
+OO.ui.ComboBoxWidget = function OoUiComboBoxWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ComboBoxWidget.super.call( this, config );
+
+ // Properties (must be set before TabIndexedElement constructor call)
+ this.$indicator = this.$( '<span>' );
+
+ // Mixin constructors
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$indicator } ) );
+
+ // Properties
+ this.$overlay = config.$overlay || this.$element;
+ this.input = new OO.ui.TextInputWidget( $.extend(
+ {
+ indicator: 'down',
+ $indicator: this.$indicator,
+ disabled: this.isDisabled()
+ },
+ config.input
+ ) );
+ this.input.$input.eq( 0 ).attr( {
+ role: 'combobox',
+ 'aria-autocomplete': 'list'
+ } );
+ this.menu = new OO.ui.TextInputMenuSelectWidget( this.input, $.extend(
+ {
+ widget: this,
+ input: this.input,
+ disabled: this.isDisabled()
+ },
+ config.menu
+ ) );
+
+ // Events
+ this.$indicator.on( {
+ click: this.onClick.bind( this ),
+ keypress: this.onKeyPress.bind( this )
+ } );
+ this.input.connect( this, {
+ change: 'onInputChange',
+ enter: 'onInputEnter'
+ } );
+ this.menu.connect( this, {
+ choose: 'onMenuChoose',
+ add: 'onMenuItemsChange',
+ remove: 'onMenuItemsChange'
+ } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-comboBoxWidget' ).append( this.input.$element );
+ this.$overlay.append( this.menu.$element );
+ this.onMenuItemsChange();
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ComboBoxWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.ComboBoxWidget, OO.ui.TabIndexedElement );
+
+/* Methods */
+
+/**
+ * Get the combobox's menu.
+ * @return {OO.ui.TextInputMenuSelectWidget} Menu widget
+ */
+OO.ui.ComboBoxWidget.prototype.getMenu = function () {
+ return this.menu;
+};
+
+/**
+ * Handle input change events.
+ *
+ * @private
+ * @param {string} value New value
+ */
+OO.ui.ComboBoxWidget.prototype.onInputChange = function ( value ) {
+ var match = this.menu.getItemFromData( value );
+
+ this.menu.selectItem( match );
+ if ( this.menu.getHighlightedItem() ) {
+ this.menu.highlightItem( match );
+ }
+
+ if ( !this.isDisabled() ) {
+ this.menu.toggle( true );
+ }
+};
+
+/**
+ * Handle mouse click events.
+ *
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse click event
+ */
+OO.ui.ComboBoxWidget.prototype.onClick = function ( e ) {
+ if ( !this.isDisabled() && e.which === 1 ) {
+ this.menu.toggle();
+ this.input.$input[ 0 ].focus();
+ }
+ return false;
+};
+
+/**
+ * Handle key press events.
+ *
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.ComboBoxWidget.prototype.onKeyPress = function ( e ) {
+ if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
+ this.menu.toggle();
+ this.input.$input[ 0 ].focus();
+ return false;
+ }
+};
+
+/**
+ * Handle input enter events.
+ *
+ * @private
+ */
+OO.ui.ComboBoxWidget.prototype.onInputEnter = function () {
+ if ( !this.isDisabled() ) {
+ this.menu.toggle( false );
+ }
+};
+
+/**
+ * Handle menu choose events.
+ *
+ * @private
+ * @param {OO.ui.OptionWidget} item Chosen item
+ */
+OO.ui.ComboBoxWidget.prototype.onMenuChoose = function ( item ) {
+ this.input.setValue( item.getData() );
+};
+
+/**
+ * Handle menu item change events.
+ *
+ * @private
+ */
+OO.ui.ComboBoxWidget.prototype.onMenuItemsChange = function () {
+ var match = this.menu.getItemFromData( this.input.getValue() );
+ this.menu.selectItem( match );
+ if ( this.menu.getHighlightedItem() ) {
+ this.menu.highlightItem( match );
+ }
+ this.$element.toggleClass( 'oo-ui-comboBoxWidget-empty', this.menu.isEmpty() );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ComboBoxWidget.prototype.setDisabled = function ( disabled ) {
+ // Parent method
+ OO.ui.ComboBoxWidget.super.prototype.setDisabled.call( this, disabled );
+
+ if ( this.input ) {
+ this.input.setDisabled( this.isDisabled() );
+ }
+ if ( this.menu ) {
+ this.menu.setDisabled( this.isDisabled() );
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/DecoratedOptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/DecoratedOptionWidget.js
new file mode 100644
index 00000000..e39ebcad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/DecoratedOptionWidget.js
@@ -0,0 +1,55 @@
+/**
+ * DecoratedOptionWidgets are {@link OO.ui.OptionWidget options} that can be configured
+ * with an {@link OO.ui.IconElement icon} and/or {@link OO.ui.IndicatorElement indicator}.
+ * This class is used with OO.ui.SelectWidget to create a selection of mutually exclusive
+ * options. For more information about options and selects, please see the
+ * [OOjs UI documentation on MediaWiki][1].
+ *
+ * @example
+ * // Decorated options in a select widget
+ * var select = new OO.ui.SelectWidget( {
+ * items: [
+ * new OO.ui.DecoratedOptionWidget( {
+ * data: 'a',
+ * label: 'Option with icon',
+ * icon: 'help'
+ * } ),
+ * new OO.ui.DecoratedOptionWidget( {
+ * data: 'b',
+ * label: 'Option with indicator',
+ * indicator: 'next'
+ * } )
+ * ]
+ * } );
+ * $( 'body' ).append( select.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ *
+ * @class
+ * @extends OO.ui.OptionWidget
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.IndicatorElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.DecoratedOptionWidget = function OoUiDecoratedOptionWidget( config ) {
+ // Parent constructor
+ OO.ui.DecoratedOptionWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, config );
+ OO.ui.IndicatorElement.call( this, config );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-decoratedOptionWidget' )
+ .prepend( this.$icon )
+ .append( this.$indicator );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.DecoratedOptionWidget, OO.ui.OptionWidget );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.IconElement );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.IndicatorElement );
diff --git a/vendor/oojs/oojs-ui/src/widgets/DropdownInputWidget.js b/vendor/oojs/oojs-ui/src/widgets/DropdownInputWidget.js
new file mode 100644
index 00000000..d4a5ce2a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/DropdownInputWidget.js
@@ -0,0 +1,137 @@
+/**
+ * DropdownInputWidget is a {@link OO.ui.DropdownWidget DropdownWidget} intended to be used
+ * within a HTML form, such as a OO.ui.FormLayout. The selected value is synchronized with the value
+ * of a hidden HTML `input` tag. Please see the [OOjs UI documentation on MediaWiki][1] for
+ * more information about input widgets.
+ *
+ * @example
+ * // Example: A DropdownInputWidget with three options
+ * var dropDown = new OO.ui.DropdownInputWidget( {
+ * label: 'Dropdown menu: Select a menu option',
+ * options: [
+ * { data: 'a', label: 'First' } ,
+ * { data: 'b', label: 'Second'} ,
+ * { data: 'c', label: 'Third' }
+ * ]
+ * } );
+ * $( 'body' ).append( dropDown.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @class
+ * @extends OO.ui.InputWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object[]} [options=[]] Array of menu options in the format `{ data: …, label: … }`
+ */
+OO.ui.DropdownInputWidget = function OoUiDropdownInputWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties (must be done before parent constructor which calls #setDisabled)
+ this.dropdownWidget = new OO.ui.DropdownWidget();
+
+ // Parent constructor
+ OO.ui.DropdownInputWidget.super.call( this, config );
+
+ // Events
+ this.dropdownWidget.getMenu().connect( this, { select: 'onMenuSelect' } );
+
+ // Initialization
+ this.setOptions( config.options || [] );
+ this.$element
+ .addClass( 'oo-ui-dropdownInputWidget' )
+ .append( this.dropdownWidget.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.DropdownInputWidget, OO.ui.InputWidget );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ * @private
+ */
+OO.ui.DropdownInputWidget.prototype.getInputElement = function () {
+ return $( '<input type="hidden">' );
+};
+
+/**
+ * Handles menu select events.
+ *
+ * @private
+ * @param {OO.ui.MenuOptionWidget} item Selected menu item
+ */
+OO.ui.DropdownInputWidget.prototype.onMenuSelect = function ( item ) {
+ this.setValue( item.getData() );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.DropdownInputWidget.prototype.setValue = function ( value ) {
+ this.dropdownWidget.getMenu().selectItemByData( value );
+ OO.ui.DropdownInputWidget.super.prototype.setValue.call( this, value );
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.DropdownInputWidget.prototype.setDisabled = function ( state ) {
+ this.dropdownWidget.setDisabled( state );
+ OO.ui.DropdownInputWidget.super.prototype.setDisabled.call( this, state );
+ return this;
+};
+
+/**
+ * Set the options available for this input.
+ *
+ * @param {Object[]} options Array of menu options in the format `{ data: …, label: … }`
+ * @chainable
+ */
+OO.ui.DropdownInputWidget.prototype.setOptions = function ( options ) {
+ var value = this.getValue();
+
+ // Rebuild the dropdown menu
+ this.dropdownWidget.getMenu()
+ .clearItems()
+ .addItems( options.map( function ( opt ) {
+ return new OO.ui.MenuOptionWidget( {
+ data: opt.data,
+ label: opt.label !== undefined ? opt.label : opt.data
+ } );
+ } ) );
+
+ // Restore the previous value, or reset to something sensible
+ if ( this.dropdownWidget.getMenu().getItemFromData( value ) ) {
+ // Previous value is still available, ensure consistency with the dropdown
+ this.setValue( value );
+ } else {
+ // No longer valid, reset
+ if ( options.length ) {
+ this.setValue( options[ 0 ].data );
+ }
+ }
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.DropdownInputWidget.prototype.focus = function () {
+ this.dropdownWidget.getMenu().toggle( true );
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.DropdownInputWidget.prototype.blur = function () {
+ this.dropdownWidget.getMenu().toggle( false );
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/DropdownWidget.js b/vendor/oojs/oojs-ui/src/widgets/DropdownWidget.js
new file mode 100644
index 00000000..f6c592e8
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/DropdownWidget.js
@@ -0,0 +1,149 @@
+/**
+ * DropdownWidgets are not menus themselves, rather they contain a menu of options created with
+ * OO.ui.MenuOptionWidget. The DropdownWidget takes care of opening and displaying the menu so that
+ * users can interact with it.
+ *
+ * @example
+ * // Example: A DropdownWidget with a menu that contains three options
+ * var dropDown = new OO.ui.DropdownWidget( {
+ * label: 'Dropdown menu: Select a menu option',
+ * menu: {
+ * items: [
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'a',
+ * label: 'First'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'b',
+ * label: 'Second'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'c',
+ * label: 'Third'
+ * } )
+ * ]
+ * }
+ * } );
+ *
+ * $( 'body' ).append( dropDown.$element );
+ *
+ * For more information, please see the [OOjs UI documentation on MediaWiki] [1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.TitledElement
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [menu] Configuration options to pass to menu widget
+ */
+OO.ui.DropdownWidget = function OoUiDropdownWidget( config ) {
+ // Configuration initialization
+ config = $.extend( { indicator: 'down' }, config );
+
+ // Parent constructor
+ OO.ui.DropdownWidget.super.call( this, config );
+
+ // Properties (must be set before TabIndexedElement constructor call)
+ this.$handle = this.$( '<span>' );
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, config );
+ OO.ui.IndicatorElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$label } ) );
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$handle } ) );
+
+ // Properties
+ this.menu = new OO.ui.MenuSelectWidget( $.extend( { widget: this }, config.menu ) );
+
+ // Events
+ this.$handle.on( {
+ click: this.onClick.bind( this ),
+ keypress: this.onKeyPress.bind( this )
+ } );
+ this.menu.connect( this, { select: 'onMenuSelect' } );
+
+ // Initialization
+ this.$handle
+ .addClass( 'oo-ui-dropdownWidget-handle' )
+ .append( this.$icon, this.$label, this.$indicator );
+ this.$element
+ .addClass( 'oo-ui-dropdownWidget' )
+ .append( this.$handle, this.menu.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.DropdownWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.IconElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.TitledElement );
+OO.mixinClass( OO.ui.DropdownWidget, OO.ui.TabIndexedElement );
+
+/* Methods */
+
+/**
+ * Get the menu.
+ *
+ * @return {OO.ui.MenuSelectWidget} Menu of widget
+ */
+OO.ui.DropdownWidget.prototype.getMenu = function () {
+ return this.menu;
+};
+
+/**
+ * Handles menu select events.
+ *
+ * @private
+ * @param {OO.ui.MenuOptionWidget} item Selected menu item
+ */
+OO.ui.DropdownWidget.prototype.onMenuSelect = function ( item ) {
+ var selectedLabel;
+
+ if ( !item ) {
+ return;
+ }
+
+ selectedLabel = item.getLabel();
+
+ // If the label is a DOM element, clone it, because setLabel will append() it
+ if ( selectedLabel instanceof jQuery ) {
+ selectedLabel = selectedLabel.clone();
+ }
+
+ this.setLabel( selectedLabel );
+};
+
+/**
+ * Handle mouse click events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse click event
+ */
+OO.ui.DropdownWidget.prototype.onClick = function ( e ) {
+ if ( !this.isDisabled() && e.which === 1 ) {
+ this.menu.toggle();
+ }
+ return false;
+};
+
+/**
+ * Handle key press events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.DropdownWidget.prototype.onKeyPress = function ( e ) {
+ if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
+ this.menu.toggle();
+ return false;
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/GroupWidget.js b/vendor/oojs/oojs-ui/src/widgets/GroupWidget.js
new file mode 100644
index 00000000..7d9be905
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/GroupWidget.js
@@ -0,0 +1,48 @@
+/**
+ * Mixin for OO.ui.Widget subclasses to provide OO.ui.GroupElement.
+ *
+ * Use together with OO.ui.ItemWidget to make disabled state inheritable.
+ *
+ * @private
+ * @abstract
+ * @class
+ * @extends OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.GroupWidget = function OoUiGroupWidget( config ) {
+ // Parent constructor
+ OO.ui.GroupWidget.super.call( this, config );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.GroupWidget, OO.ui.GroupElement );
+
+/* Methods */
+
+/**
+ * Set the disabled state of the widget.
+ *
+ * This will also update the disabled state of child widgets.
+ *
+ * @param {boolean} disabled Disable widget
+ * @chainable
+ */
+OO.ui.GroupWidget.prototype.setDisabled = function ( disabled ) {
+ var i, len;
+
+ // Parent method
+ // Note: Calling #setDisabled this way assumes this is mixed into an OO.ui.Widget
+ OO.ui.Widget.prototype.setDisabled.call( this, disabled );
+
+ // During construction, #setDisabled is called before the OO.ui.GroupElement constructor
+ if ( this.items ) {
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ this.items[ i ].updateDisabled();
+ }
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/IconWidget.js b/vendor/oojs/oojs-ui/src/widgets/IconWidget.js
new file mode 100644
index 00000000..66ea3871
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/IconWidget.js
@@ -0,0 +1,54 @@
+/**
+ * IconWidget is a generic widget for {@link OO.ui.IconElement icons}. In general, IconWidgets should be used with OO.ui.LabelWidget,
+ * which creates a label that identifies the icon’s function. See the [OOjs UI documentation on MediaWiki] [1]
+ * for a list of icons included in the library.
+ *
+ * @example
+ * // An icon widget with a label
+ * var myIcon = new OO.ui.IconWidget( {
+ * icon: 'help',
+ * iconTitle: 'Help'
+ * } );
+ * // Create a label.
+ * var iconLabel = new OO.ui.LabelWidget( {
+ * label: 'Help'
+ * } );
+ * $( 'body' ).append( myIcon.$element, iconLabel.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Icons
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.TitledElement
+ * @mixins OO.ui.FlaggedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.IconWidget = function OoUiIconWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.IconWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, $.extend( {}, config, { $icon: this.$element } ) );
+ OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) );
+ OO.ui.FlaggedElement.call( this, $.extend( {}, config, { $flagged: this.$element } ) );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-iconWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.IconWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.IconWidget, OO.ui.IconElement );
+OO.mixinClass( OO.ui.IconWidget, OO.ui.TitledElement );
+OO.mixinClass( OO.ui.IconWidget, OO.ui.FlaggedElement );
+
+/* Static Properties */
+
+OO.ui.IconWidget.static.tagName = 'span';
diff --git a/vendor/oojs/oojs-ui/src/widgets/IndicatorWidget.js b/vendor/oojs/oojs-ui/src/widgets/IndicatorWidget.js
new file mode 100644
index 00000000..e6ecddf0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/IndicatorWidget.js
@@ -0,0 +1,52 @@
+/**
+ * IndicatorWidgets create indicators, which are small graphics that are generally used to draw
+ * attention to the status of an item or to clarify the function of a control. For a list of
+ * indicators included in the library, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * @example
+ * // Example of an indicator widget
+ * var indicator1 = new OO.ui.IndicatorWidget( {
+ * indicator: 'alert'
+ * } );
+ *
+ * // Create a fieldset layout to add a label
+ * var fieldset = new OO.ui.FieldsetLayout();
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( indicator1, { label: 'An alert indicator:' } )
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Icons,_Indicators,_and_Labels#Indicators
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.TitledElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.IndicatorWidget = function OoUiIndicatorWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.IndicatorWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.IndicatorElement.call( this, $.extend( {}, config, { $indicator: this.$element } ) );
+ OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$element } ) );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-indicatorWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.IndicatorWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.IndicatorWidget, OO.ui.TitledElement );
+
+/* Static Properties */
+
+OO.ui.IndicatorWidget.static.tagName = 'span';
diff --git a/vendor/oojs/oojs-ui/src/widgets/InputWidget.js b/vendor/oojs/oojs-ui/src/widgets/InputWidget.js
new file mode 100644
index 00000000..de790bac
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/InputWidget.js
@@ -0,0 +1,207 @@
+/**
+ * InputWidget is the base class for all input widgets, which
+ * include {@link OO.ui.TextInputWidget text inputs}, {@link OO.ui.CheckboxInputWidget checkbox inputs},
+ * {@link OO.ui.RadioInputWidget radio inputs}, and {@link OO.ui.ButtonInputWidget button inputs}.
+ * See the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.FlaggedElement
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [name=''] The value of the input’s HTML `name` attribute.
+ * @cfg {string} [value=''] The value of the input.
+ * @cfg {Function} [inputFilter] The name of an input filter function. Input filters modify the value of an input
+ * before it is accepted.
+ */
+OO.ui.InputWidget = function OoUiInputWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.InputWidget.super.call( this, config );
+
+ // Properties
+ this.$input = this.getInputElement( config );
+ this.value = '';
+ this.inputFilter = config.inputFilter;
+
+ // Mixin constructors
+ OO.ui.FlaggedElement.call( this, config );
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$input } ) );
+
+ // Events
+ this.$input.on( 'keydown mouseup cut paste change input select', this.onEdit.bind( this ) );
+
+ // Initialization
+ this.$input
+ .attr( 'name', config.name )
+ .prop( 'disabled', this.isDisabled() );
+ this.$element.addClass( 'oo-ui-inputWidget' ).append( this.$input, $( '<span>' ) );
+ this.setValue( config.value );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.InputWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.InputWidget, OO.ui.FlaggedElement );
+OO.mixinClass( OO.ui.InputWidget, OO.ui.TabIndexedElement );
+
+/* Events */
+
+/**
+ * @event change
+ *
+ * A change event is emitted when the value of the input changes.
+ *
+ * @param {string} value
+ */
+
+/* Methods */
+
+/**
+ * Get input element.
+ *
+ * Subclasses of OO.ui.InputWidget use the `config` parameter to produce different elements in
+ * different circumstances. The element must have a `value` property (like form elements).
+ *
+ * @private
+ * @param {Object} config Configuration options
+ * @return {jQuery} Input element
+ */
+OO.ui.InputWidget.prototype.getInputElement = function () {
+ return $( '<input>' );
+};
+
+/**
+ * Handle potentially value-changing events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key down, mouse up, cut, paste, change, input, or select event
+ */
+OO.ui.InputWidget.prototype.onEdit = function () {
+ var widget = this;
+ if ( !this.isDisabled() ) {
+ // Allow the stack to clear so the value will be updated
+ setTimeout( function () {
+ widget.setValue( widget.$input.val() );
+ } );
+ }
+};
+
+/**
+ * Get the value of the input.
+ *
+ * @return {string} Input value
+ */
+OO.ui.InputWidget.prototype.getValue = function () {
+ // Resynchronize our internal data with DOM data. Other scripts executing on the page can modify
+ // it, and we won't know unless they're kind enough to trigger a 'change' event.
+ var value = this.$input.val();
+ if ( this.value !== value ) {
+ this.setValue( value );
+ }
+ return this.value;
+};
+
+/**
+ * Set the direction of the input, either RTL (right-to-left) or LTR (left-to-right).
+ *
+ * @param {boolean} isRTL
+ * Direction is right-to-left
+ */
+OO.ui.InputWidget.prototype.setRTL = function ( isRTL ) {
+ this.$input.prop( 'dir', isRTL ? 'rtl' : 'ltr' );
+};
+
+/**
+ * Set the value of the input.
+ *
+ * @param {string} value New value
+ * @fires change
+ * @chainable
+ */
+OO.ui.InputWidget.prototype.setValue = function ( value ) {
+ value = this.cleanUpValue( value );
+ // Update the DOM if it has changed. Note that with cleanUpValue, it
+ // is possible for the DOM value to change without this.value changing.
+ if ( this.$input.val() !== value ) {
+ this.$input.val( value );
+ }
+ if ( this.value !== value ) {
+ this.value = value;
+ this.emit( 'change', this.value );
+ }
+ return this;
+};
+
+/**
+ * Clean up incoming value.
+ *
+ * Ensures value is a string, and converts undefined and null to empty string.
+ *
+ * @private
+ * @param {string} value Original value
+ * @return {string} Cleaned up value
+ */
+OO.ui.InputWidget.prototype.cleanUpValue = function ( value ) {
+ if ( value === undefined || value === null ) {
+ return '';
+ } else if ( this.inputFilter ) {
+ return this.inputFilter( String( value ) );
+ } else {
+ return String( value );
+ }
+};
+
+/**
+ * Simulate the behavior of clicking on a label bound to this input. This method is only called by
+ * {@link OO.ui.LabelWidget LabelWidget} and {@link OO.ui.FieldLayout FieldLayout}. It should not be
+ * called directly.
+ */
+OO.ui.InputWidget.prototype.simulateLabelClick = function () {
+ if ( !this.isDisabled() ) {
+ if ( this.$input.is( ':checkbox, :radio' ) ) {
+ this.$input.click();
+ }
+ if ( this.$input.is( ':input' ) ) {
+ this.$input[ 0 ].focus();
+ }
+ }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.InputWidget.prototype.setDisabled = function ( state ) {
+ OO.ui.InputWidget.super.prototype.setDisabled.call( this, state );
+ if ( this.$input ) {
+ this.$input.prop( 'disabled', this.isDisabled() );
+ }
+ return this;
+};
+
+/**
+ * Focus the input.
+ *
+ * @chainable
+ */
+OO.ui.InputWidget.prototype.focus = function () {
+ this.$input[ 0 ].focus();
+ return this;
+};
+
+/**
+ * Blur the input.
+ *
+ * @chainable
+ */
+OO.ui.InputWidget.prototype.blur = function () {
+ this.$input[ 0 ].blur();
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ItemWidget.js b/vendor/oojs/oojs-ui/src/widgets/ItemWidget.js
new file mode 100644
index 00000000..292514f4
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ItemWidget.js
@@ -0,0 +1,48 @@
+/**
+ * Mixin for widgets used as items in widgets that inherit OO.ui.GroupWidget.
+ *
+ * Item widgets have a reference to a OO.ui.GroupWidget while they are attached to the group. This
+ * allows bidirectional communication.
+ *
+ * Use together with OO.ui.GroupWidget to make disabled state inheritable.
+ *
+ * @private
+ * @abstract
+ * @class
+ *
+ * @constructor
+ */
+OO.ui.ItemWidget = function OoUiItemWidget() {
+ //
+};
+
+/* Methods */
+
+/**
+ * Check if widget is disabled.
+ *
+ * Checks parent if present, making disabled state inheritable.
+ *
+ * @return {boolean} Widget is disabled
+ */
+OO.ui.ItemWidget.prototype.isDisabled = function () {
+ return this.disabled ||
+ ( this.elementGroup instanceof OO.ui.Widget && this.elementGroup.isDisabled() );
+};
+
+/**
+ * Set group element is in.
+ *
+ * @param {OO.ui.GroupElement|null} group Group element, null if none
+ * @chainable
+ */
+OO.ui.ItemWidget.prototype.setElementGroup = function ( group ) {
+ // Parent method
+ // Note: Calling #setElementGroup this way assumes this is mixed into an OO.ui.Element
+ OO.ui.Element.prototype.setElementGroup.call( this, group );
+
+ // Initialize item disabled states
+ this.updateDisabled();
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/LabelWidget.js b/vendor/oojs/oojs-ui/src/widgets/LabelWidget.js
new file mode 100644
index 00000000..e00990bb
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/LabelWidget.js
@@ -0,0 +1,84 @@
+/**
+ * LabelWidgets help identify the function of interface elements. Each LabelWidget can
+ * be configured with a `label` option that is set to a string, a label node, or a function:
+ *
+ * - String: a plaintext string
+ * - jQuery selection: a jQuery selection, used for anything other than a plaintext label, e.g., a
+ * label that includes a link or special styling, such as a gray color or additional graphical elements.
+ * - Function: a function that will produce a string in the future. Functions are used
+ * in cases where the value of the label is not currently defined.
+ *
+ * In addition, the LabelWidget can be associated with an {@link OO.ui.InputWidget input widget}, which
+ * will come into focus when the label is clicked.
+ *
+ * @example
+ * // Examples of LabelWidgets
+ * var label1 = new OO.ui.LabelWidget( {
+ * label: 'plaintext label'
+ * } );
+ * var label2 = new OO.ui.LabelWidget( {
+ * label: $( '<a href="default.html">jQuery label</a>' )
+ * } );
+ * // Create a fieldset layout with fields for each example
+ * var fieldset = new OO.ui.FieldsetLayout();
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( label1 ),
+ * new OO.ui.FieldLayout( label2 )
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.LabelElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.InputWidget} [input] {@link OO.ui.InputWidget Input widget} that uses the label.
+ * Clicking the label will focus the specified input field.
+ */
+OO.ui.LabelWidget = function OoUiLabelWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.LabelWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.LabelElement.call( this, $.extend( {}, config, { $label: this.$element } ) );
+ OO.ui.TitledElement.call( this, config );
+
+ // Properties
+ this.input = config.input;
+
+ // Events
+ if ( this.input instanceof OO.ui.InputWidget ) {
+ this.$element.on( 'click', this.onClick.bind( this ) );
+ }
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-labelWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.LabelWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.LabelWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.LabelWidget, OO.ui.TitledElement );
+
+/* Static Properties */
+
+OO.ui.LabelWidget.static.tagName = 'span';
+
+/* Methods */
+
+/**
+ * Handles label mouse click events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse click event
+ */
+OO.ui.LabelWidget.prototype.onClick = function () {
+ this.input.simulateLabelClick();
+ return false;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/MenuOptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/MenuOptionWidget.js
new file mode 100644
index 00000000..cc620d97
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/MenuOptionWidget.js
@@ -0,0 +1,33 @@
+/**
+ * MenuOptionWidget is an option widget that looks like a menu item. The class is used with
+ * OO.ui.MenuSelectWidget to create a menu of mutually exclusive options. Please see
+ * the [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Menu_selects_and_options
+ *
+ * @class
+ * @extends OO.ui.DecoratedOptionWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.MenuOptionWidget = function OoUiMenuOptionWidget( config ) {
+ // Configuration initialization
+ config = $.extend( { icon: 'check' }, config );
+
+ // Parent constructor
+ OO.ui.MenuOptionWidget.super.call( this, config );
+
+ // Initialization
+ this.$element
+ .attr( 'role', 'menuitem' )
+ .addClass( 'oo-ui-menuOptionWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MenuOptionWidget, OO.ui.DecoratedOptionWidget );
+
+/* Static Properties */
+
+OO.ui.MenuOptionWidget.static.scrollIntoViewOnSelect = true;
diff --git a/vendor/oojs/oojs-ui/src/widgets/MenuSectionOptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/MenuSectionOptionWidget.js
new file mode 100644
index 00000000..054df7fc
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/MenuSectionOptionWidget.js
@@ -0,0 +1,55 @@
+/**
+ * MenuSectionOptionWidgets are used inside {@link OO.ui.MenuSelectWidget menu select widgets} to group one or more related
+ * {@link OO.ui.MenuOptionWidget menu options}. MenuSectionOptionWidgets cannot be highlighted or selected.
+ *
+ * @example
+ * var myDropdown = new OO.ui.DropdownWidget( {
+ * menu: {
+ * items: [
+ * new OO.ui.MenuSectionOptionWidget( {
+ * label: 'Dogs'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'corgi',
+ * label: 'Welsh Corgi'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'poodle',
+ * label: 'Standard Poodle'
+ * } ),
+ * new OO.ui.MenuSectionOptionWidget( {
+ * label: 'Cats'
+ * } ),
+ * new OO.ui.MenuOptionWidget( {
+ * data: 'lion',
+ * label: 'Lion'
+ * } )
+ * ]
+ * }
+ * } );
+ * $( 'body' ).append( myDropdown.$element );
+ *
+ *
+ * @class
+ * @extends OO.ui.DecoratedOptionWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.MenuSectionOptionWidget = function OoUiMenuSectionOptionWidget( config ) {
+ // Parent constructor
+ OO.ui.MenuSectionOptionWidget.super.call( this, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-menuSectionOptionWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MenuSectionOptionWidget, OO.ui.DecoratedOptionWidget );
+
+/* Static Properties */
+
+OO.ui.MenuSectionOptionWidget.static.selectable = false;
+
+OO.ui.MenuSectionOptionWidget.static.highlightable = false;
diff --git a/vendor/oojs/oojs-ui/src/widgets/MenuSelectWidget.js b/vendor/oojs/oojs-ui/src/widgets/MenuSelectWidget.js
new file mode 100644
index 00000000..a1755edd
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/MenuSelectWidget.js
@@ -0,0 +1,254 @@
+/**
+ * MenuSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains options and
+ * is used together with OO.ui.MenuOptionWidget. It is designed be used as part of another widget.
+ * See {@link OO.ui.DropdownWidget DropdownWidget}, {@link OO.ui.ComboBoxWidget ComboBoxWidget},
+ * and {@link OO.ui.LookupElement LookupElement} for examples of widgets that contain menus.
+ * MenuSelectWidgets themselves are not instantiated directly, rather subclassed
+ * and customized to be opened, closed, and displayed as needed.
+ *
+ * By default, menus are clipped to the visible viewport and are not visible when a user presses the
+ * mouse outside the menu.
+ *
+ * Menus also have support for keyboard interaction:
+ *
+ * - Enter/Return key: choose and select a menu option
+ * - Up-arrow key: highlight the previous menu option
+ * - Down-arrow key: highlight the next menu option
+ * - Esc key: hide the menu
+ *
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ *
+ * @class
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.ClippableElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.TextInputWidget} [input] Text input used to implement option highlighting for menu items that match
+ * the text the user types. This config is used by {@link OO.ui.ComboBoxWidget ComboBoxWidget}
+ * and {@link OO.ui.LookupElement LookupElement}
+ * @cfg {OO.ui.Widget} [widget] Widget associated with the menu’s active state. If the user clicks the mouse
+ * anywhere on the page outside of this widget, the menu is hidden.
+ * @cfg {boolean} [autoHide=true] Hide the menu when the mouse is pressed outside the menu.
+ */
+OO.ui.MenuSelectWidget = function OoUiMenuSelectWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.MenuSelectWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$group } ) );
+
+ // Properties
+ this.newItems = null;
+ this.autoHide = config.autoHide === undefined || !!config.autoHide;
+ this.$input = config.input ? config.input.$input : null;
+ this.$widget = config.widget ? config.widget.$element : null;
+ this.onDocumentMouseDownHandler = this.onDocumentMouseDown.bind( this );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-menuSelectWidget' )
+ .attr( 'role', 'menu' );
+
+ // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
+ // that reference properties not initialized at that time of parent class construction
+ // TODO: Find a better way to handle post-constructor setup
+ this.visible = false;
+ this.$element.addClass( 'oo-ui-element-hidden' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.MenuSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.MenuSelectWidget, OO.ui.ClippableElement );
+
+/* Methods */
+
+/**
+ * Handles document mouse down events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.MenuSelectWidget.prototype.onDocumentMouseDown = function ( e ) {
+ if (
+ !OO.ui.contains( this.$element[ 0 ], e.target, true ) &&
+ ( !this.$widget || !OO.ui.contains( this.$widget[ 0 ], e.target, true ) )
+ ) {
+ this.toggle( false );
+ }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.onKeyDown = function ( e ) {
+ var currentItem = this.getHighlightedItem() || this.getSelectedItem();
+
+ if ( !this.isDisabled() && this.isVisible() ) {
+ switch ( e.keyCode ) {
+ case OO.ui.Keys.LEFT:
+ case OO.ui.Keys.RIGHT:
+ // Do nothing if a text field is associated, arrow keys will be handled natively
+ if ( !this.$input ) {
+ OO.ui.MenuSelectWidget.super.prototype.onKeyDown.call( this, e );
+ }
+ break;
+ case OO.ui.Keys.ESCAPE:
+ case OO.ui.Keys.TAB:
+ if ( currentItem ) {
+ currentItem.setHighlighted( false );
+ }
+ this.toggle( false );
+ // Don't prevent tabbing away, prevent defocusing
+ if ( e.keyCode === OO.ui.Keys.ESCAPE ) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ break;
+ default:
+ OO.ui.MenuSelectWidget.super.prototype.onKeyDown.call( this, e );
+ return;
+ }
+ }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.bindKeyDownListener = function () {
+ if ( this.$input ) {
+ this.$input.on( 'keydown', this.onKeyDownHandler );
+ } else {
+ OO.ui.MenuSelectWidget.super.prototype.bindKeyDownListener.call( this );
+ }
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.unbindKeyDownListener = function () {
+ if ( this.$input ) {
+ this.$input.off( 'keydown', this.onKeyDownHandler );
+ } else {
+ OO.ui.MenuSelectWidget.super.prototype.unbindKeyDownListener.call( this );
+ }
+};
+
+/**
+ * Choose an item.
+ *
+ * When a user chooses an item, the menu is closed.
+ *
+ * Note that ‘choose’ should never be modified programmatically. A user can choose an option with the keyboard
+ * or mouse and it becomes selected. To select an item programmatically, use the #selectItem method.
+ * @param {OO.ui.OptionWidget} item Item to choose
+ * @chainable
+ */
+OO.ui.MenuSelectWidget.prototype.chooseItem = function ( item ) {
+ OO.ui.MenuSelectWidget.super.prototype.chooseItem.call( this, item );
+ this.toggle( false );
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.addItems = function ( items, index ) {
+ var i, len, item;
+
+ // Parent method
+ OO.ui.MenuSelectWidget.super.prototype.addItems.call( this, items, index );
+
+ // Auto-initialize
+ if ( !this.newItems ) {
+ this.newItems = [];
+ }
+
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ item = items[ i ];
+ if ( this.isVisible() ) {
+ // Defer fitting label until item has been attached
+ item.fitLabel();
+ } else {
+ this.newItems.push( item );
+ }
+ }
+
+ // Reevaluate clipping
+ this.clip();
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.removeItems = function ( items ) {
+ // Parent method
+ OO.ui.MenuSelectWidget.super.prototype.removeItems.call( this, items );
+
+ // Reevaluate clipping
+ this.clip();
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.clearItems = function () {
+ // Parent method
+ OO.ui.MenuSelectWidget.super.prototype.clearItems.call( this );
+
+ // Reevaluate clipping
+ this.clip();
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.MenuSelectWidget.prototype.toggle = function ( visible ) {
+ visible = ( visible === undefined ? !this.visible : !!visible ) && !!this.items.length;
+
+ var i, len,
+ change = visible !== this.isVisible();
+
+ // Parent method
+ OO.ui.MenuSelectWidget.super.prototype.toggle.call( this, visible );
+
+ if ( change ) {
+ if ( visible ) {
+ this.bindKeyDownListener();
+
+ if ( this.newItems && this.newItems.length ) {
+ for ( i = 0, len = this.newItems.length; i < len; i++ ) {
+ this.newItems[ i ].fitLabel();
+ }
+ this.newItems = null;
+ }
+ this.toggleClipping( true );
+
+ // Auto-hide
+ if ( this.autoHide ) {
+ this.getElementDocument().addEventListener(
+ 'mousedown', this.onDocumentMouseDownHandler, true
+ );
+ }
+ } else {
+ this.unbindKeyDownListener();
+ this.getElementDocument().removeEventListener(
+ 'mousedown', this.onDocumentMouseDownHandler, true
+ );
+ this.toggleClipping( false );
+ }
+ }
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/OptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/OptionWidget.js
new file mode 100644
index 00000000..0bc2486e
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/OptionWidget.js
@@ -0,0 +1,177 @@
+/**
+ * OptionWidgets are special elements that can be selected and configured with data. The
+ * data is often unique for each option, but it does not have to be. OptionWidgets are used
+ * with OO.ui.SelectWidget to create a selection of mutually exclusive options. For more information
+ * and examples, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.FlaggedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.OptionWidget = function OoUiOptionWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.OptionWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.ItemWidget.call( this );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.FlaggedElement.call( this, config );
+
+ // Properties
+ this.selected = false;
+ this.highlighted = false;
+ this.pressed = false;
+
+ // Initialization
+ this.$element
+ .data( 'oo-ui-optionWidget', this )
+ .attr( 'role', 'option' )
+ .addClass( 'oo-ui-optionWidget' )
+ .append( this.$label );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.OptionWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.ItemWidget );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.OptionWidget, OO.ui.FlaggedElement );
+
+/* Static Properties */
+
+OO.ui.OptionWidget.static.selectable = true;
+
+OO.ui.OptionWidget.static.highlightable = true;
+
+OO.ui.OptionWidget.static.pressable = true;
+
+OO.ui.OptionWidget.static.scrollIntoViewOnSelect = false;
+
+/* Methods */
+
+/**
+ * Check if the option can be selected.
+ *
+ * @return {boolean} Item is selectable
+ */
+OO.ui.OptionWidget.prototype.isSelectable = function () {
+ return this.constructor.static.selectable && !this.isDisabled();
+};
+
+/**
+ * Check if the option can be highlighted. A highlight indicates that the option
+ * may be selected when a user presses enter or clicks. Disabled items cannot
+ * be highlighted.
+ *
+ * @return {boolean} Item is highlightable
+ */
+OO.ui.OptionWidget.prototype.isHighlightable = function () {
+ return this.constructor.static.highlightable && !this.isDisabled();
+};
+
+/**
+ * Check if the option can be pressed. The pressed state occurs when a user mouses
+ * down on an item, but has not yet let go of the mouse.
+ *
+ * @return {boolean} Item is pressable
+ */
+OO.ui.OptionWidget.prototype.isPressable = function () {
+ return this.constructor.static.pressable && !this.isDisabled();
+};
+
+/**
+ * Check if the option is selected.
+ *
+ * @return {boolean} Item is selected
+ */
+OO.ui.OptionWidget.prototype.isSelected = function () {
+ return this.selected;
+};
+
+/**
+ * Check if the option is highlighted. A highlight indicates that the
+ * item may be selected when a user presses enter or clicks.
+ *
+ * @return {boolean} Item is highlighted
+ */
+OO.ui.OptionWidget.prototype.isHighlighted = function () {
+ return this.highlighted;
+};
+
+/**
+ * Check if the option is pressed. The pressed state occurs when a user mouses
+ * down on an item, but has not yet let go of the mouse. The item may appear
+ * selected, but it will not be selected until the user releases the mouse.
+ *
+ * @return {boolean} Item is pressed
+ */
+OO.ui.OptionWidget.prototype.isPressed = function () {
+ return this.pressed;
+};
+
+/**
+ * Set the option’s selected state. In general, all modifications to the selection
+ * should be handled by the SelectWidget’s {@link OO.ui.SelectWidget#selectItem selectItem( [item] )}
+ * method instead of this method.
+ *
+ * @param {boolean} [state=false] Select option
+ * @chainable
+ */
+OO.ui.OptionWidget.prototype.setSelected = function ( state ) {
+ if ( this.constructor.static.selectable ) {
+ this.selected = !!state;
+ this.$element
+ .toggleClass( 'oo-ui-optionWidget-selected', state )
+ .attr( 'aria-selected', state.toString() );
+ if ( state && this.constructor.static.scrollIntoViewOnSelect ) {
+ this.scrollElementIntoView();
+ }
+ this.updateThemeClasses();
+ }
+ return this;
+};
+
+/**
+ * Set the option’s highlighted state. In general, all programmatic
+ * modifications to the highlight should be handled by the
+ * SelectWidget’s {@link OO.ui.SelectWidget#highlightItem highlightItem( [item] )}
+ * method instead of this method.
+ *
+ * @param {boolean} [state=false] Highlight option
+ * @chainable
+ */
+OO.ui.OptionWidget.prototype.setHighlighted = function ( state ) {
+ if ( this.constructor.static.highlightable ) {
+ this.highlighted = !!state;
+ this.$element.toggleClass( 'oo-ui-optionWidget-highlighted', state );
+ this.updateThemeClasses();
+ }
+ return this;
+};
+
+/**
+ * Set the option’s pressed state. In general, all
+ * programmatic modifications to the pressed state should be handled by the
+ * SelectWidget’s {@link OO.ui.SelectWidget#pressItem pressItem( [item] )}
+ * method instead of this method.
+ *
+ * @param {boolean} [state=false] Press option
+ * @chainable
+ */
+OO.ui.OptionWidget.prototype.setPressed = function ( state ) {
+ if ( this.constructor.static.pressable ) {
+ this.pressed = !!state;
+ this.$element.toggleClass( 'oo-ui-optionWidget-pressed', state );
+ this.updateThemeClasses();
+ }
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/OutlineControlsWidget.js b/vendor/oojs/oojs-ui/src/widgets/OutlineControlsWidget.js
new file mode 100644
index 00000000..0219a44d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/OutlineControlsWidget.js
@@ -0,0 +1,145 @@
+/**
+ * OutlineControlsWidget is a set of controls for an {@link OO.ui.OutlineSelectWidget outline select widget}.
+ * Controls include moving items up and down, removing items, and adding different kinds of items.
+ * ####Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}.####
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.GroupElement
+ * @mixins OO.ui.IconElement
+ *
+ * @constructor
+ * @param {OO.ui.OutlineSelectWidget} outline Outline to control
+ * @param {Object} [config] Configuration options
+ * @cfg {Object} [abilities] List of abilties
+ * @cfg {boolean} [abilities.move=true] Allow moving movable items
+ * @cfg {boolean} [abilities.remove=true] Allow removing removable items
+ */
+OO.ui.OutlineControlsWidget = function OoUiOutlineControlsWidget( outline, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( outline ) && config === undefined ) {
+ config = outline;
+ outline = config.outline;
+ }
+
+ // Configuration initialization
+ config = $.extend( { icon: 'add' }, config );
+
+ // Parent constructor
+ OO.ui.OutlineControlsWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.GroupElement.call( this, config );
+ OO.ui.IconElement.call( this, config );
+
+ // Properties
+ this.outline = outline;
+ this.$movers = $( '<div>' );
+ this.upButton = new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'collapse',
+ title: OO.ui.msg( 'ooui-outline-control-move-up' )
+ } );
+ this.downButton = new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'expand',
+ title: OO.ui.msg( 'ooui-outline-control-move-down' )
+ } );
+ this.removeButton = new OO.ui.ButtonWidget( {
+ framed: false,
+ icon: 'remove',
+ title: OO.ui.msg( 'ooui-outline-control-remove' )
+ } );
+ this.abilities = { move: true, remove: true };
+
+ // Events
+ outline.connect( this, {
+ select: 'onOutlineChange',
+ add: 'onOutlineChange',
+ remove: 'onOutlineChange'
+ } );
+ this.upButton.connect( this, { click: [ 'emit', 'move', -1 ] } );
+ this.downButton.connect( this, { click: [ 'emit', 'move', 1 ] } );
+ this.removeButton.connect( this, { click: [ 'emit', 'remove' ] } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-outlineControlsWidget' );
+ this.$group.addClass( 'oo-ui-outlineControlsWidget-items' );
+ this.$movers
+ .addClass( 'oo-ui-outlineControlsWidget-movers' )
+ .append( this.removeButton.$element, this.upButton.$element, this.downButton.$element );
+ this.$element.append( this.$icon, this.$group, this.$movers );
+ this.setAbilities( config.abilities || {} );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.OutlineControlsWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.GroupElement );
+OO.mixinClass( OO.ui.OutlineControlsWidget, OO.ui.IconElement );
+
+/* Events */
+
+/**
+ * @event move
+ * @param {number} places Number of places to move
+ */
+
+/**
+ * @event remove
+ */
+
+/* Methods */
+
+/**
+ * Set abilities.
+ *
+ * @param {Object} abilities List of abilties
+ * @param {boolean} [abilities.move] Allow moving movable items
+ * @param {boolean} [abilities.remove] Allow removing removable items
+ */
+OO.ui.OutlineControlsWidget.prototype.setAbilities = function ( abilities ) {
+ var ability;
+
+ for ( ability in this.abilities ) {
+ if ( abilities[ability] !== undefined ) {
+ this.abilities[ability] = !!abilities[ability];
+ }
+ }
+
+ this.onOutlineChange();
+};
+
+/**
+ *
+ * @private
+ * Handle outline change events.
+ */
+OO.ui.OutlineControlsWidget.prototype.onOutlineChange = function () {
+ var i, len, firstMovable, lastMovable,
+ items = this.outline.getItems(),
+ selectedItem = this.outline.getSelectedItem(),
+ movable = this.abilities.move && selectedItem && selectedItem.isMovable(),
+ removable = this.abilities.remove && selectedItem && selectedItem.isRemovable();
+
+ if ( movable ) {
+ i = -1;
+ len = items.length;
+ while ( ++i < len ) {
+ if ( items[ i ].isMovable() ) {
+ firstMovable = items[ i ];
+ break;
+ }
+ }
+ i = len;
+ while ( i-- ) {
+ if ( items[ i ].isMovable() ) {
+ lastMovable = items[ i ];
+ break;
+ }
+ }
+ }
+ this.upButton.setDisabled( !movable || selectedItem === firstMovable );
+ this.downButton.setDisabled( !movable || selectedItem === lastMovable );
+ this.removeButton.setDisabled( !removable );
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/OutlineOptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/OutlineOptionWidget.js
new file mode 100644
index 00000000..d792fee6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/OutlineOptionWidget.js
@@ -0,0 +1,130 @@
+/**
+ * OutlineOptionWidget is an item in an {@link OO.ui.OutlineSelectWidget OutlineSelectWidget}.
+ *
+ * Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}, which contain
+ * {@link OO.ui.PageLayout page layouts}. See {@link OO.ui.BookletLayout BookletLayout}
+ * for an example.
+ *
+ * @class
+ * @extends OO.ui.DecoratedOptionWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number} [level] Indentation level
+ * @cfg {boolean} [movable] Allow modification from {@link OO.ui.OutlineControlsWidget outline controls}.
+ */
+OO.ui.OutlineOptionWidget = function OoUiOutlineOptionWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.OutlineOptionWidget.super.call( this, config );
+
+ // Properties
+ this.level = 0;
+ this.movable = !!config.movable;
+ this.removable = !!config.removable;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-outlineOptionWidget' );
+ this.setLevel( config.level );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.OutlineOptionWidget, OO.ui.DecoratedOptionWidget );
+
+/* Static Properties */
+
+OO.ui.OutlineOptionWidget.static.highlightable = false;
+
+OO.ui.OutlineOptionWidget.static.scrollIntoViewOnSelect = true;
+
+OO.ui.OutlineOptionWidget.static.levelClass = 'oo-ui-outlineOptionWidget-level-';
+
+OO.ui.OutlineOptionWidget.static.levels = 3;
+
+/* Methods */
+
+/**
+ * Check if item is movable.
+ *
+ * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ *
+ * @return {boolean} Item is movable
+ */
+OO.ui.OutlineOptionWidget.prototype.isMovable = function () {
+ return this.movable;
+};
+
+/**
+ * Check if item is removable.
+ *
+ * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ *
+ * @return {boolean} Item is removable
+ */
+OO.ui.OutlineOptionWidget.prototype.isRemovable = function () {
+ return this.removable;
+};
+
+/**
+ * Get indentation level.
+ *
+ * @return {number} Indentation level
+ */
+OO.ui.OutlineOptionWidget.prototype.getLevel = function () {
+ return this.level;
+};
+
+/**
+ * Set movability.
+ *
+ * Movability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ *
+ * @param {boolean} movable Item is movable
+ * @chainable
+ */
+OO.ui.OutlineOptionWidget.prototype.setMovable = function ( movable ) {
+ this.movable = !!movable;
+ this.updateThemeClasses();
+ return this;
+};
+
+/**
+ * Set removability.
+ *
+ * Removability is used by {@link OO.ui.OutlineControlsWidget outline controls}.
+ *
+ * @param {boolean} movable Item is removable
+ * @chainable
+ */
+OO.ui.OutlineOptionWidget.prototype.setRemovable = function ( removable ) {
+ this.removable = !!removable;
+ this.updateThemeClasses();
+ return this;
+};
+
+/**
+ * Set indentation level.
+ *
+ * @param {number} [level=0] Indentation level, in the range of [0,#maxLevel]
+ * @chainable
+ */
+OO.ui.OutlineOptionWidget.prototype.setLevel = function ( level ) {
+ var levels = this.constructor.static.levels,
+ levelClass = this.constructor.static.levelClass,
+ i = levels;
+
+ this.level = level ? Math.max( 0, Math.min( levels - 1, level ) ) : 0;
+ while ( i-- ) {
+ if ( this.level === i ) {
+ this.$element.addClass( levelClass + i );
+ } else {
+ this.$element.removeClass( levelClass + i );
+ }
+ }
+ this.updateThemeClasses();
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/OutlineSelectWidget.js b/vendor/oojs/oojs-ui/src/widgets/OutlineSelectWidget.js
new file mode 100644
index 00000000..cd7db546
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/OutlineSelectWidget.js
@@ -0,0 +1,34 @@
+/**
+ * OutlineSelectWidget is a structured list that contains {@link OO.ui.OutlineOptionWidget outline options}
+ * A set of controls can be provided with an {@link OO.ui.OutlineControlsWidget outline controls} widget.
+ *
+ * ####Currently, this class is only used by {@link OO.ui.BookletLayout booklet layouts}.####
+ *
+ * @class
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.OutlineSelectWidget = function OoUiOutlineSelectWidget( config ) {
+ // Parent constructor
+ OO.ui.OutlineSelectWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.TabIndexedElement.call( this, config );
+
+ // Events
+ this.$element.on( {
+ focus: this.bindKeyDownListener.bind( this ),
+ blur: this.unbindKeyDownListener.bind( this )
+ } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-outlineSelectWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.OutlineSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.OutlineSelectWidget, OO.ui.TabIndexedElement );
diff --git a/vendor/oojs/oojs-ui/src/widgets/PopupButtonWidget.js b/vendor/oojs/oojs-ui/src/widgets/PopupButtonWidget.js
new file mode 100644
index 00000000..5643b77c
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/PopupButtonWidget.js
@@ -0,0 +1,57 @@
+/**
+ * PopupButtonWidgets toggle the visibility of a contained {@link OO.ui.PopupWidget PopupWidget},
+ * which is used to display additional information or options.
+ *
+ * @example
+ * // Example of a popup button.
+ * var popupButton = new OO.ui.PopupButtonWidget( {
+ * label: 'Popup button with options',
+ * icon: 'menu',
+ * popup: {
+ * $content: $( '<p>Additional options here.</p>' ),
+ * padded: true,
+ * align: 'force-left'
+ * }
+ * } );
+ * // Append the button to the DOM.
+ * $( 'body' ).append( popupButton.$element );
+ *
+ * @class
+ * @extends OO.ui.ButtonWidget
+ * @mixins OO.ui.PopupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.PopupButtonWidget = function OoUiPopupButtonWidget( config ) {
+ // Parent constructor
+ OO.ui.PopupButtonWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.PopupElement.call( this, config );
+
+ // Events
+ this.connect( this, { click: 'onAction' } );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-popupButtonWidget' )
+ .attr( 'aria-haspopup', 'true' )
+ .append( this.popup.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PopupButtonWidget, OO.ui.ButtonWidget );
+OO.mixinClass( OO.ui.PopupButtonWidget, OO.ui.PopupElement );
+
+/* Methods */
+
+/**
+ * Handle the button action being triggered.
+ *
+ * @private
+ */
+OO.ui.PopupButtonWidget.prototype.onAction = function () {
+ this.popup.toggle();
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/PopupWidget.js b/vendor/oojs/oojs-ui/src/widgets/PopupWidget.js
new file mode 100644
index 00000000..0b1f4ca6
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/PopupWidget.js
@@ -0,0 +1,395 @@
+/**
+ * PopupWidget is a container for content. The popup is overlaid and positioned absolutely.
+ * By default, each popup has an anchor that points toward its origin.
+ * Please see the [OOjs UI documentation on Mediawiki] [1] for more information and examples.
+ *
+ * @example
+ * // A popup widget.
+ * var popup = new OO.ui.PopupWidget( {
+ * $content: $( '<p>Hi there!</p>' ),
+ * padded: true,
+ * width: 300
+ * } );
+ *
+ * $( 'body' ).append( popup.$element );
+ * // To display the popup, toggle the visibility to 'true'.
+ * popup.toggle( true );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups
+ *
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.LabelElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number} [width=320] Width of popup in pixels
+ * @cfg {number} [height] Height of popup in pixels. Omit to use the automatic height.
+ * @cfg {boolean} [anchor=true] Show anchor pointing to origin of popup
+ * @cfg {string} [align='center'] Alignment of the popup: `center`, `force-left`, `force-right`, `backwards` or `forwards`.
+ * If the popup is forced-left the popup body is leaning towards the left. For force-right alignment, the body of the
+ * popup is leaning towards the right of the screen.
+ * Using 'backwards' is a logical direction which will result in the popup leaning towards the beginning of the sentence
+ * in the given language, which means it will flip to the correct positioning in right-to-left languages.
+ * Using 'forward' will also result in a logical alignment where the body of the popup leans towards the end of the
+ * sentence in the given language.
+ * @cfg {jQuery} [$container] Constrain the popup to the boundaries of the specified container.
+ * See the [OOjs UI docs on MediaWiki][3] for an example.
+ * [3]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#containerExample
+ * @cfg {number} [containerPadding=10] Padding between the popup and its container, specified as a number of pixels.
+ * @cfg {jQuery} [$content] Content to append to the popup's body
+ * @cfg {boolean} [autoClose=false] Automatically close the popup when it loses focus.
+ * @cfg {jQuery} [$autoCloseIgnore] Elements that will not close the popup when clicked.
+ * This config option is only relevant if #autoClose is set to `true`. See the [OOjs UI docs on MediaWiki][2]
+ * for an example.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Popups#autocloseExample
+ * @cfg {boolean} [head] Show a popup header that contains a #label (if specified) and close
+ * button.
+ * @cfg {boolean} [padded] Add padding to the popup's body
+ */
+OO.ui.PopupWidget = function OoUiPopupWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.PopupWidget.super.call( this, config );
+
+ // Properties (must be set before ClippableElement constructor call)
+ this.$body = $( '<div>' );
+
+ // Mixin constructors
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.ClippableElement.call( this, $.extend( {}, config, { $clippable: this.$body } ) );
+
+ // Properties
+ this.$popup = $( '<div>' );
+ this.$head = $( '<div>' );
+ this.$anchor = $( '<div>' );
+ // If undefined, will be computed lazily in updateDimensions()
+ this.$container = config.$container;
+ this.containerPadding = config.containerPadding !== undefined ? config.containerPadding : 10;
+ this.autoClose = !!config.autoClose;
+ this.$autoCloseIgnore = config.$autoCloseIgnore;
+ this.transitionTimeout = null;
+ this.anchor = null;
+ this.width = config.width !== undefined ? config.width : 320;
+ this.height = config.height !== undefined ? config.height : null;
+ this.setAlignment( config.align );
+ this.closeButton = new OO.ui.ButtonWidget( { framed: false, icon: 'close' } );
+ this.onMouseDownHandler = this.onMouseDown.bind( this );
+ this.onDocumentKeyDownHandler = this.onDocumentKeyDown.bind( this );
+
+ // Events
+ this.closeButton.connect( this, { click: 'onCloseButtonClick' } );
+
+ // Initialization
+ this.toggleAnchor( config.anchor === undefined || config.anchor );
+ this.$body.addClass( 'oo-ui-popupWidget-body' );
+ this.$anchor.addClass( 'oo-ui-popupWidget-anchor' );
+ this.$head
+ .addClass( 'oo-ui-popupWidget-head' )
+ .append( this.$label, this.closeButton.$element );
+ if ( !config.head ) {
+ this.$head.addClass( 'oo-ui-element-hidden' );
+ }
+ this.$popup
+ .addClass( 'oo-ui-popupWidget-popup' )
+ .append( this.$head, this.$body );
+ this.$element
+ .addClass( 'oo-ui-popupWidget' )
+ .append( this.$popup, this.$anchor );
+ // Move content, which was added to #$element by OO.ui.Widget, to the body
+ if ( config.$content instanceof jQuery ) {
+ this.$body.append( config.$content );
+ }
+ if ( config.padded ) {
+ this.$body.addClass( 'oo-ui-popupWidget-body-padded' );
+ }
+
+ // Initially hidden - using #toggle may cause errors if subclasses override toggle with methods
+ // that reference properties not initialized at that time of parent class construction
+ // TODO: Find a better way to handle post-constructor setup
+ this.visible = false;
+ this.$element.addClass( 'oo-ui-element-hidden' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.PopupWidget, OO.ui.Widget );
+OO.mixinClass( OO.ui.PopupWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.PopupWidget, OO.ui.ClippableElement );
+
+/* Methods */
+
+/**
+ * Handles mouse down events.
+ *
+ * @private
+ * @param {MouseEvent} e Mouse down event
+ */
+OO.ui.PopupWidget.prototype.onMouseDown = function ( e ) {
+ if (
+ this.isVisible() &&
+ !$.contains( this.$element[ 0 ], e.target ) &&
+ ( !this.$autoCloseIgnore || !this.$autoCloseIgnore.has( e.target ).length )
+ ) {
+ this.toggle( false );
+ }
+};
+
+/**
+ * Bind mouse down listener.
+ *
+ * @private
+ */
+OO.ui.PopupWidget.prototype.bindMouseDownListener = function () {
+ // Capture clicks outside popup
+ this.getElementWindow().addEventListener( 'mousedown', this.onMouseDownHandler, true );
+};
+
+/**
+ * Handles close button click events.
+ *
+ * @private
+ */
+OO.ui.PopupWidget.prototype.onCloseButtonClick = function () {
+ if ( this.isVisible() ) {
+ this.toggle( false );
+ }
+};
+
+/**
+ * Unbind mouse down listener.
+ *
+ * @private
+ */
+OO.ui.PopupWidget.prototype.unbindMouseDownListener = function () {
+ this.getElementWindow().removeEventListener( 'mousedown', this.onMouseDownHandler, true );
+};
+
+/**
+ * Handles key down events.
+ *
+ * @private
+ * @param {KeyboardEvent} e Key down event
+ */
+OO.ui.PopupWidget.prototype.onDocumentKeyDown = function ( e ) {
+ if (
+ e.which === OO.ui.Keys.ESCAPE &&
+ this.isVisible()
+ ) {
+ this.toggle( false );
+ e.preventDefault();
+ e.stopPropagation();
+ }
+};
+
+/**
+ * Bind key down listener.
+ *
+ * @private
+ */
+OO.ui.PopupWidget.prototype.bindKeyDownListener = function () {
+ this.getElementWindow().addEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
+};
+
+/**
+ * Unbind key down listener.
+ *
+ * @private
+ */
+OO.ui.PopupWidget.prototype.unbindKeyDownListener = function () {
+ this.getElementWindow().removeEventListener( 'keydown', this.onDocumentKeyDownHandler, true );
+};
+
+/**
+ * Show, hide, or toggle the visibility of the anchor.
+ *
+ * @param {boolean} [show] Show anchor, omit to toggle
+ */
+OO.ui.PopupWidget.prototype.toggleAnchor = function ( show ) {
+ show = show === undefined ? !this.anchored : !!show;
+
+ if ( this.anchored !== show ) {
+ if ( show ) {
+ this.$element.addClass( 'oo-ui-popupWidget-anchored' );
+ } else {
+ this.$element.removeClass( 'oo-ui-popupWidget-anchored' );
+ }
+ this.anchored = show;
+ }
+};
+
+/**
+ * Check if the anchor is visible.
+ *
+ * @return {boolean} Anchor is visible
+ */
+OO.ui.PopupWidget.prototype.hasAnchor = function () {
+ return this.anchor;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.PopupWidget.prototype.toggle = function ( show ) {
+ show = show === undefined ? !this.isVisible() : !!show;
+
+ var change = show !== this.isVisible();
+
+ // Parent method
+ OO.ui.PopupWidget.super.prototype.toggle.call( this, show );
+
+ if ( change ) {
+ if ( show ) {
+ if ( this.autoClose ) {
+ this.bindMouseDownListener();
+ this.bindKeyDownListener();
+ }
+ this.updateDimensions();
+ this.toggleClipping( true );
+ } else {
+ this.toggleClipping( false );
+ if ( this.autoClose ) {
+ this.unbindMouseDownListener();
+ this.unbindKeyDownListener();
+ }
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Set the size of the popup.
+ *
+ * Changing the size may also change the popup's position depending on the alignment.
+ *
+ * @param {number} width Width in pixels
+ * @param {number} height Height in pixels
+ * @param {boolean} [transition=false] Use a smooth transition
+ * @chainable
+ */
+OO.ui.PopupWidget.prototype.setSize = function ( width, height, transition ) {
+ this.width = width;
+ this.height = height !== undefined ? height : null;
+ if ( this.isVisible() ) {
+ this.updateDimensions( transition );
+ }
+};
+
+/**
+ * Update the size and position.
+ *
+ * Only use this to keep the popup properly anchored. Use #setSize to change the size, and this will
+ * be called automatically.
+ *
+ * @param {boolean} [transition=false] Use a smooth transition
+ * @chainable
+ */
+OO.ui.PopupWidget.prototype.updateDimensions = function ( transition ) {
+ var popupOffset, originOffset, containerLeft, containerWidth, containerRight,
+ popupLeft, popupRight, overlapLeft, overlapRight, anchorWidth,
+ align = this.align,
+ widget = this;
+
+ if ( !this.$container ) {
+ // Lazy-initialize $container if not specified in constructor
+ this.$container = $( this.getClosestScrollableElementContainer() );
+ }
+
+ // Set height and width before measuring things, since it might cause our measurements
+ // to change (e.g. due to scrollbars appearing or disappearing)
+ this.$popup.css( {
+ width: this.width,
+ height: this.height !== null ? this.height : 'auto'
+ } );
+
+ // If we are in RTL, we need to flip the alignment, unless it is center
+ if ( align === 'forwards' || align === 'backwards' ) {
+ if ( this.$container.css( 'direction' ) === 'rtl' ) {
+ align = ( { forwards: 'force-left', backwards: 'force-right' } )[ this.align ];
+ } else {
+ align = ( { forwards: 'force-right', backwards: 'force-left' } )[ this.align ];
+ }
+
+ }
+
+ // Compute initial popupOffset based on alignment
+ popupOffset = this.width * ( { 'force-left': -1, center: -0.5, 'force-right': 0 } )[ align ];
+
+ // Figure out if this will cause the popup to go beyond the edge of the container
+ originOffset = this.$element.offset().left;
+ containerLeft = this.$container.offset().left;
+ containerWidth = this.$container.innerWidth();
+ containerRight = containerLeft + containerWidth;
+ popupLeft = popupOffset - this.containerPadding;
+ popupRight = popupOffset + this.containerPadding + this.width + this.containerPadding;
+ overlapLeft = ( originOffset + popupLeft ) - containerLeft;
+ overlapRight = containerRight - ( originOffset + popupRight );
+
+ // Adjust offset to make the popup not go beyond the edge, if needed
+ if ( overlapRight < 0 ) {
+ popupOffset += overlapRight;
+ } else if ( overlapLeft < 0 ) {
+ popupOffset -= overlapLeft;
+ }
+
+ // Adjust offset to avoid anchor being rendered too close to the edge
+ // $anchor.width() doesn't work with the pure CSS anchor (returns 0)
+ // TODO: Find a measurement that works for CSS anchors and image anchors
+ anchorWidth = this.$anchor[ 0 ].scrollWidth * 2;
+ if ( popupOffset + this.width < anchorWidth ) {
+ popupOffset = anchorWidth - this.width;
+ } else if ( -popupOffset < anchorWidth ) {
+ popupOffset = -anchorWidth;
+ }
+
+ // Prevent transition from being interrupted
+ clearTimeout( this.transitionTimeout );
+ if ( transition ) {
+ // Enable transition
+ this.$element.addClass( 'oo-ui-popupWidget-transitioning' );
+ }
+
+ // Position body relative to anchor
+ this.$popup.css( 'margin-left', popupOffset );
+
+ if ( transition ) {
+ // Prevent transitioning after transition is complete
+ this.transitionTimeout = setTimeout( function () {
+ widget.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
+ }, 200 );
+ } else {
+ // Prevent transitioning immediately
+ this.$element.removeClass( 'oo-ui-popupWidget-transitioning' );
+ }
+
+ // Reevaluate clipping state since we've relocated and resized the popup
+ this.clip();
+
+ return this;
+};
+
+/**
+ * Set popup alignment
+ * @param {string} align Alignment of the popup, `center`, `force-left`, `force-right`,
+ * `backwards` or `forwards`.
+ */
+OO.ui.PopupWidget.prototype.setAlignment = function ( align ) {
+ // Validate alignment and transform deprecated values
+ if ( [ 'left', 'right', 'force-left', 'force-right', 'backwards', 'forwards', 'center' ].indexOf( align ) > -1 ) {
+ this.align = { left: 'force-right', right: 'force-left' }[ align ] || align;
+ } else {
+ this.align = 'center';
+ }
+};
+
+/**
+ * Get popup alignment
+ * @return {string} align Alignment of the popup, `center`, `force-left`, `force-right`,
+ * `backwards` or `forwards`.
+ */
+OO.ui.PopupWidget.prototype.getAlignment = function () {
+ return this.align;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ProgressBarWidget.js b/vendor/oojs/oojs-ui/src/widgets/ProgressBarWidget.js
new file mode 100644
index 00000000..a4676282
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ProgressBarWidget.js
@@ -0,0 +1,96 @@
+/**
+ * Progress bars visually display the status of an operation, such as a download,
+ * and can be either determinate or indeterminate:
+ *
+ * - **determinate** process bars show the percent of an operation that is complete.
+ *
+ * - **indeterminate** process bars use a visual display of motion to indicate that an operation
+ * is taking place. Because the extent of an indeterminate operation is unknown, the bar does
+ * not use percentages.
+ *
+ * The value of the `progress` configuration determines whether the bar is determinate or indeterminate.
+ *
+ * @example
+ * // Examples of determinate and indeterminate progress bars.
+ * var progressBar1 = new OO.ui.ProgressBarWidget( {
+ * progress: 33
+ * } );
+ * var progressBar2 = new OO.ui.ProgressBarWidget();
+ *
+ * // Create a FieldsetLayout to layout progress bars
+ * var fieldset = new OO.ui.FieldsetLayout;
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( progressBar1, {label: 'Determinate', align: 'top'}),
+ * new OO.ui.FieldLayout( progressBar2, {label: 'Indeterminate', align: 'top'})
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {number|boolean} [progress=false] The type of progress bar (determinate or indeterminate).
+ * To create a determinate progress bar, specify a number that reflects the initial percent complete.
+ * By default, the progress bar is indeterminate.
+ */
+OO.ui.ProgressBarWidget = function OoUiProgressBarWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ProgressBarWidget.super.call( this, config );
+
+ // Properties
+ this.$bar = $( '<div>' );
+ this.progress = null;
+
+ // Initialization
+ this.setProgress( config.progress !== undefined ? config.progress : false );
+ this.$bar.addClass( 'oo-ui-progressBarWidget-bar' );
+ this.$element
+ .attr( {
+ role: 'progressbar',
+ 'aria-valuemin': 0,
+ 'aria-valuemax': 100
+ } )
+ .addClass( 'oo-ui-progressBarWidget' )
+ .append( this.$bar );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ProgressBarWidget, OO.ui.Widget );
+
+/* Static Properties */
+
+OO.ui.ProgressBarWidget.static.tagName = 'div';
+
+/* Methods */
+
+/**
+ * Get the percent of the progress that has been completed. Indeterminate progresses will return `false`.
+ *
+ * @return {number|boolean} Progress percent
+ */
+OO.ui.ProgressBarWidget.prototype.getProgress = function () {
+ return this.progress;
+};
+
+/**
+ * Set the percent of the process completed or `false` for an indeterminate process.
+ *
+ * @param {number|boolean} progress Progress percent or `false` for indeterminate
+ */
+OO.ui.ProgressBarWidget.prototype.setProgress = function ( progress ) {
+ this.progress = progress;
+
+ if ( progress !== false ) {
+ this.$bar.css( 'width', this.progress + '%' );
+ this.$element.attr( 'aria-valuenow', this.progress );
+ } else {
+ this.$bar.css( 'width', '' );
+ this.$element.removeAttr( 'aria-valuenow' );
+ }
+ this.$element.toggleClass( 'oo-ui-progressBarWidget-indeterminate', !progress );
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/RadioInputWidget.js b/vendor/oojs/oojs-ui/src/widgets/RadioInputWidget.js
new file mode 100644
index 00000000..47ac20bf
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/RadioInputWidget.js
@@ -0,0 +1,94 @@
+/**
+ * RadioInputWidget creates a single radio button. Because radio buttons are usually used as a set,
+ * in most cases you will want to use a {@link OO.ui.RadioSelectWidget radio select}
+ * with {@link OO.ui.RadioOptionWidget radio options} instead of this class. For more information,
+ * please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
+ *
+ * @example
+ * // An example of selected, unselected, and disabled radio inputs
+ * var radio1 = new OO.ui.RadioInputWidget( {
+ * value: 'a',
+ * selected: true
+ * } );
+ * var radio2 = new OO.ui.RadioInputWidget( {
+ * value: 'b'
+ * } );
+ * var radio3 = new OO.ui.RadioInputWidget( {
+ * value: 'c',
+ * disabled: true
+ * } );
+ * // Create a fieldset layout with fields for each radio button.
+ * var fieldset = new OO.ui.FieldsetLayout( {
+ * label: 'Radio inputs'
+ * } );
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( radio1, { label: 'Selected', align: 'inline' } ),
+ * new OO.ui.FieldLayout( radio2, { label: 'Unselected', align: 'inline' } ),
+ * new OO.ui.FieldLayout( radio3, { label: 'Disabled', align: 'inline' } ),
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @class
+ * @extends OO.ui.InputWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [selected=false] Select the radio button initially. By default, the radio button is not selected.
+ */
+OO.ui.RadioInputWidget = function OoUiRadioInputWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.RadioInputWidget.super.call( this, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-radioInputWidget' );
+ this.setSelected( config.selected !== undefined ? config.selected : false );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.RadioInputWidget, OO.ui.InputWidget );
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ * @private
+ */
+OO.ui.RadioInputWidget.prototype.getInputElement = function () {
+ return $( '<input type="radio" />' );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.RadioInputWidget.prototype.onEdit = function () {
+ // RadioInputWidget doesn't track its state.
+};
+
+/**
+ * Set selection state of this radio button.
+ *
+ * @param {boolean} state `true` for selected
+ * @chainable
+ */
+OO.ui.RadioInputWidget.prototype.setSelected = function ( state ) {
+ // RadioInputWidget doesn't track its state.
+ this.$input.prop( 'checked', state );
+ return this;
+};
+
+/**
+ * Check if this radio button is selected.
+ *
+ * @return {boolean} Radio is selected
+ */
+OO.ui.RadioInputWidget.prototype.isSelected = function () {
+ return this.$input.prop( 'checked' );
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/RadioOptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/RadioOptionWidget.js
new file mode 100644
index 00000000..97187c0a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/RadioOptionWidget.js
@@ -0,0 +1,66 @@
+/**
+ * RadioOptionWidget is an option widget that looks like a radio button.
+ * The class is used with OO.ui.RadioSelectWidget to create a selection of radio options.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information.
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options#Button_selects_and_option
+ *
+ * @class
+ * @extends OO.ui.OptionWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.RadioOptionWidget = function OoUiRadioOptionWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Properties (must be done before parent constructor which calls #setDisabled)
+ this.radio = new OO.ui.RadioInputWidget( { value: config.data, tabIndex: -1 } );
+
+ // Parent constructor
+ OO.ui.RadioOptionWidget.super.call( this, config );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-radioOptionWidget' )
+ .prepend( this.radio.$element );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.RadioOptionWidget, OO.ui.OptionWidget );
+
+/* Static Properties */
+
+OO.ui.RadioOptionWidget.static.highlightable = false;
+
+OO.ui.RadioOptionWidget.static.scrollIntoViewOnSelect = true;
+
+OO.ui.RadioOptionWidget.static.pressable = false;
+
+OO.ui.RadioOptionWidget.static.tagName = 'label';
+
+/* Methods */
+
+/**
+ * @inheritdoc
+ */
+OO.ui.RadioOptionWidget.prototype.setSelected = function ( state ) {
+ OO.ui.RadioOptionWidget.super.prototype.setSelected.call( this, state );
+
+ this.radio.setSelected( state );
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.RadioOptionWidget.prototype.setDisabled = function ( disabled ) {
+ OO.ui.RadioOptionWidget.super.prototype.setDisabled.call( this, disabled );
+
+ this.radio.setDisabled( this.isDisabled() );
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/RadioSelectWidget.js b/vendor/oojs/oojs-ui/src/widgets/RadioSelectWidget.js
new file mode 100644
index 00000000..3859205f
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/RadioSelectWidget.js
@@ -0,0 +1,58 @@
+/**
+ * RadioSelectWidget is a {@link OO.ui.SelectWidget select widget} that contains radio
+ * options and is used together with OO.ui.RadioOptionWidget. The RadioSelectWidget provides
+ * an interface for adding, removing and selecting options.
+ * Please see the [OOjs UI documentation on MediaWiki][1] for more information.
+ *
+ * @example
+ * // A RadioSelectWidget with RadioOptions.
+ * var option1 = new OO.ui.RadioOptionWidget( {
+ * data: 'a',
+ * label: 'Selected radio option'
+ * } );
+ *
+ * var option2 = new OO.ui.RadioOptionWidget( {
+ * data: 'b',
+ * label: 'Unselected radio option'
+ * } );
+ *
+ * var radioSelect=new OO.ui.RadioSelectWidget( {
+ * items: [ option1, option2 ]
+ * } );
+ *
+ * // Select 'option 1' using the RadioSelectWidget's selectItem() method.
+ * radioSelect.selectItem( option1 );
+ *
+ * $( 'body' ).append( radioSelect.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+
+ *
+ * @class
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.RadioSelectWidget = function OoUiRadioSelectWidget( config ) {
+ // Parent constructor
+ OO.ui.RadioSelectWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.TabIndexedElement.call( this, config );
+
+ // Events
+ this.$element.on( {
+ focus: this.bindKeyDownListener.bind( this ),
+ blur: this.unbindKeyDownListener.bind( this )
+ } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-radioSelectWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.RadioSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.RadioSelectWidget, OO.ui.TabIndexedElement );
diff --git a/vendor/oojs/oojs-ui/src/widgets/SearchWidget.js b/vendor/oojs/oojs-ui/src/widgets/SearchWidget.js
new file mode 100644
index 00000000..4909d9e1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/SearchWidget.js
@@ -0,0 +1,176 @@
+/**
+ * SearchWidgets combine a {@link OO.ui.TextInputWidget text input field}, where users can type a search query,
+ * and a {@link OO.ui.TextInputMenuSelectWidget menu} of search results, which is displayed beneath the query
+ * field. Unlike {@link OO.ui.LookupElement lookup menus}, search result menus are always visible to the user.
+ * Users can choose an item from the menu or type a query into the text field to search for a matching result item.
+ * In general, search widgets are used inside a separate {@link OO.ui.Dialog dialog} window.
+ *
+ * Each time the query is changed, the search result menu is cleared and repopulated. Please see
+ * the [OOjs UI demos][1] for an example.
+ *
+ * [1]: https://tools.wmflabs.org/oojs-ui/oojs-ui/demos/#dialogs-mediawiki-vector-ltr
+ *
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string|jQuery} [placeholder] Placeholder text for query input
+ * @cfg {string} [value] Initial query value
+ */
+OO.ui.SearchWidget = function OoUiSearchWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.SearchWidget.super.call( this, config );
+
+ // Properties
+ this.query = new OO.ui.TextInputWidget( {
+ icon: 'search',
+ placeholder: config.placeholder,
+ value: config.value
+ } );
+ this.results = new OO.ui.SelectWidget();
+ this.$query = $( '<div>' );
+ this.$results = $( '<div>' );
+
+ // Events
+ this.query.connect( this, {
+ change: 'onQueryChange',
+ enter: 'onQueryEnter'
+ } );
+ this.results.connect( this, {
+ highlight: 'onResultsHighlight',
+ select: 'onResultsSelect'
+ } );
+ this.query.$input.on( 'keydown', this.onQueryKeydown.bind( this ) );
+
+ // Initialization
+ this.$query
+ .addClass( 'oo-ui-searchWidget-query' )
+ .append( this.query.$element );
+ this.$results
+ .addClass( 'oo-ui-searchWidget-results' )
+ .append( this.results.$element );
+ this.$element
+ .addClass( 'oo-ui-searchWidget' )
+ .append( this.$results, this.$query );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.SearchWidget, OO.ui.Widget );
+
+/* Events */
+
+/**
+ * A 'highlight' event is emitted when an item is highlighted. The highlight indicates which
+ * item will be selected. When a user mouses over a menu item, it is highlighted. If a search
+ * string is typed into the query field instead, the first menu item that matches the query
+ * will be highlighted.
+
+ * @event highlight
+ * @deprecated Connect straight to getResults() events instead
+ * @param {Object|null} item Item data or null if no item is highlighted
+ */
+
+/**
+ * A 'select' event is emitted when an item is selected. A menu item is selected when it is clicked,
+ * or when a user types a search query, a menu result is highlighted, and the user presses enter.
+ *
+ * @event select
+ * @deprecated Connect straight to getResults() events instead
+ * @param {Object|null} item Item data or null if no item is selected
+ */
+
+/* Methods */
+
+/**
+ * Handle query key down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.SearchWidget.prototype.onQueryKeydown = function ( e ) {
+ var highlightedItem, nextItem,
+ dir = e.which === OO.ui.Keys.DOWN ? 1 : ( e.which === OO.ui.Keys.UP ? -1 : 0 );
+
+ if ( dir ) {
+ highlightedItem = this.results.getHighlightedItem();
+ if ( !highlightedItem ) {
+ highlightedItem = this.results.getSelectedItem();
+ }
+ nextItem = this.results.getRelativeSelectableItem( highlightedItem, dir );
+ this.results.highlightItem( nextItem );
+ nextItem.scrollElementIntoView();
+ }
+};
+
+/**
+ * Handle select widget select events.
+ *
+ * Clears existing results. Subclasses should repopulate items according to new query.
+ *
+ * @private
+ * @param {string} value New value
+ */
+OO.ui.SearchWidget.prototype.onQueryChange = function () {
+ // Reset
+ this.results.clearItems();
+};
+
+/**
+ * Handle select widget enter key events.
+ *
+ * Selects highlighted item.
+ *
+ * @private
+ * @param {string} value New value
+ */
+OO.ui.SearchWidget.prototype.onQueryEnter = function () {
+ // Reset
+ this.results.selectItem( this.results.getHighlightedItem() );
+};
+
+/**
+ * Handle select widget highlight events.
+ *
+ * @private
+ * @deprecated Connect straight to getResults() events instead
+ * @param {OO.ui.OptionWidget} item Highlighted item
+ * @fires highlight
+ */
+OO.ui.SearchWidget.prototype.onResultsHighlight = function ( item ) {
+ this.emit( 'highlight', item ? item.getData() : null );
+};
+
+/**
+ * Handle select widget select events.
+ *
+ * @private
+ * @deprecated Connect straight to getResults() events instead
+ * @param {OO.ui.OptionWidget} item Selected item
+ * @fires select
+ */
+OO.ui.SearchWidget.prototype.onResultsSelect = function ( item ) {
+ this.emit( 'select', item ? item.getData() : null );
+};
+
+/**
+ * Get the query input.
+ *
+ * @return {OO.ui.TextInputWidget} Query input
+ */
+OO.ui.SearchWidget.prototype.getQuery = function () {
+ return this.query;
+};
+
+/**
+ * Get the search results menu.
+ *
+ * @return {OO.ui.SelectWidget} Menu of search results
+ */
+OO.ui.SearchWidget.prototype.getResults = function () {
+ return this.results;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/SelectWidget.js b/vendor/oojs/oojs-ui/src/widgets/SelectWidget.js
new file mode 100644
index 00000000..91172af0
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/SelectWidget.js
@@ -0,0 +1,630 @@
+/**
+ * A SelectWidget is of a generic selection of options. The OOjs UI library contains several types of
+ * select widgets, including {@link OO.ui.ButtonSelectWidget button selects},
+ * {@link OO.ui.RadioSelectWidget radio selects}, and {@link OO.ui.MenuSelectWidget
+ * menu selects}.
+ *
+ * This class should be used together with OO.ui.OptionWidget or OO.ui.DecoratedOptionWidget. For more
+ * information, please see the [OOjs UI documentation on MediaWiki][1].
+ *
+ * @example
+ * // Example of a select widget with three options
+ * var select = new OO.ui.SelectWidget( {
+ * items: [
+ * new OO.ui.OptionWidget( {
+ * data: 'a',
+ * label: 'Option One',
+ * } ),
+ * new OO.ui.OptionWidget( {
+ * data: 'b',
+ * label: 'Option Two',
+ * } ),
+ * new OO.ui.OptionWidget( {
+ * data: 'c',
+ * label: 'Option Three',
+ * } )
+ * ]
+ * } );
+ * $( 'body' ).append( select.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Widget
+ * @mixins OO.ui.GroupElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {OO.ui.OptionWidget[]} [items] An array of options to add to the select.
+ * Options are created with {@link OO.ui.OptionWidget OptionWidget} classes. See
+ * the [OOjs UI documentation on MediaWiki] [2] for examples.
+ * [2]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Selects_and_Options
+ */
+OO.ui.SelectWidget = function OoUiSelectWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.SelectWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.GroupWidget.call( this, $.extend( {}, config, { $group: this.$element } ) );
+
+ // Properties
+ this.pressed = false;
+ this.selecting = null;
+ this.onMouseUpHandler = this.onMouseUp.bind( this );
+ this.onMouseMoveHandler = this.onMouseMove.bind( this );
+ this.onKeyDownHandler = this.onKeyDown.bind( this );
+
+ // Events
+ this.$element.on( {
+ mousedown: this.onMouseDown.bind( this ),
+ mouseover: this.onMouseOver.bind( this ),
+ mouseleave: this.onMouseLeave.bind( this )
+ } );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-selectWidget oo-ui-selectWidget-depressed' )
+ .attr( 'role', 'listbox' );
+ if ( Array.isArray( config.items ) ) {
+ this.addItems( config.items );
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.SelectWidget, OO.ui.Widget );
+
+// Need to mixin base class as well
+OO.mixinClass( OO.ui.SelectWidget, OO.ui.GroupElement );
+OO.mixinClass( OO.ui.SelectWidget, OO.ui.GroupWidget );
+
+/* Events */
+
+/**
+ * @event highlight
+ *
+ * A `highlight` event is emitted when the highlight is changed with the #highlightItem method.
+ *
+ * @param {OO.ui.OptionWidget|null} item Highlighted item
+ */
+
+/**
+ * @event press
+ *
+ * A `press` event is emitted when the #pressItem method is used to programmatically modify the
+ * pressed state of an option.
+ *
+ * @param {OO.ui.OptionWidget|null} item Pressed item
+ */
+
+/**
+ * @event select
+ *
+ * A `select` event is emitted when the selection is modified programmatically with the #selectItem method.
+ *
+ * @param {OO.ui.OptionWidget|null} item Selected item
+ */
+
+/**
+ * @event choose
+ * A `choose` event is emitted when an item is chosen with the #chooseItem method.
+ * @param {OO.ui.OptionWidget} item Chosen item
+ */
+
+/**
+ * @event add
+ *
+ * An `add` event is emitted when options are added to the select with the #addItems method.
+ *
+ * @param {OO.ui.OptionWidget[]} items Added items
+ * @param {number} index Index of insertion point
+ */
+
+/**
+ * @event remove
+ *
+ * A `remove` event is emitted when options are removed from the select with the #clearItems
+ * or #removeItems methods.
+ *
+ * @param {OO.ui.OptionWidget[]} items Removed items
+ */
+
+/* Methods */
+
+/**
+ * Handle mouse down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse down event
+ */
+OO.ui.SelectWidget.prototype.onMouseDown = function ( e ) {
+ var item;
+
+ if ( !this.isDisabled() && e.which === 1 ) {
+ this.togglePressed( true );
+ item = this.getTargetItem( e );
+ if ( item && item.isSelectable() ) {
+ this.pressItem( item );
+ this.selecting = item;
+ this.getElementDocument().addEventListener(
+ 'mouseup',
+ this.onMouseUpHandler,
+ true
+ );
+ this.getElementDocument().addEventListener(
+ 'mousemove',
+ this.onMouseMoveHandler,
+ true
+ );
+ }
+ }
+ return false;
+};
+
+/**
+ * Handle mouse up events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse up event
+ */
+OO.ui.SelectWidget.prototype.onMouseUp = function ( e ) {
+ var item;
+
+ this.togglePressed( false );
+ if ( !this.selecting ) {
+ item = this.getTargetItem( e );
+ if ( item && item.isSelectable() ) {
+ this.selecting = item;
+ }
+ }
+ if ( !this.isDisabled() && e.which === 1 && this.selecting ) {
+ this.pressItem( null );
+ this.chooseItem( this.selecting );
+ this.selecting = null;
+ }
+
+ this.getElementDocument().removeEventListener(
+ 'mouseup',
+ this.onMouseUpHandler,
+ true
+ );
+ this.getElementDocument().removeEventListener(
+ 'mousemove',
+ this.onMouseMoveHandler,
+ true
+ );
+
+ return false;
+};
+
+/**
+ * Handle mouse move events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse move event
+ */
+OO.ui.SelectWidget.prototype.onMouseMove = function ( e ) {
+ var item;
+
+ if ( !this.isDisabled() && this.pressed ) {
+ item = this.getTargetItem( e );
+ if ( item && item !== this.selecting && item.isSelectable() ) {
+ this.pressItem( item );
+ this.selecting = item;
+ }
+ }
+ return false;
+};
+
+/**
+ * Handle mouse over events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse over event
+ */
+OO.ui.SelectWidget.prototype.onMouseOver = function ( e ) {
+ var item;
+
+ if ( !this.isDisabled() ) {
+ item = this.getTargetItem( e );
+ this.highlightItem( item && item.isHighlightable() ? item : null );
+ }
+ return false;
+};
+
+/**
+ * Handle mouse leave events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse over event
+ */
+OO.ui.SelectWidget.prototype.onMouseLeave = function () {
+ if ( !this.isDisabled() ) {
+ this.highlightItem( null );
+ }
+ return false;
+};
+
+/**
+ * Handle key down events.
+ *
+ * @protected
+ * @param {jQuery.Event} e Key down event
+ */
+OO.ui.SelectWidget.prototype.onKeyDown = function ( e ) {
+ var nextItem,
+ handled = false,
+ currentItem = this.getHighlightedItem() || this.getSelectedItem();
+
+ if ( !this.isDisabled() && this.isVisible() ) {
+ switch ( e.keyCode ) {
+ case OO.ui.Keys.ENTER:
+ if ( currentItem && currentItem.constructor.static.highlightable ) {
+ // Was only highlighted, now let's select it. No-op if already selected.
+ this.chooseItem( currentItem );
+ handled = true;
+ }
+ break;
+ case OO.ui.Keys.UP:
+ case OO.ui.Keys.LEFT:
+ nextItem = this.getRelativeSelectableItem( currentItem, -1 );
+ handled = true;
+ break;
+ case OO.ui.Keys.DOWN:
+ case OO.ui.Keys.RIGHT:
+ nextItem = this.getRelativeSelectableItem( currentItem, 1 );
+ handled = true;
+ break;
+ case OO.ui.Keys.ESCAPE:
+ case OO.ui.Keys.TAB:
+ if ( currentItem && currentItem.constructor.static.highlightable ) {
+ currentItem.setHighlighted( false );
+ }
+ this.unbindKeyDownListener();
+ // Don't prevent tabbing away / defocusing
+ handled = false;
+ break;
+ }
+
+ if ( nextItem ) {
+ if ( nextItem.constructor.static.highlightable ) {
+ this.highlightItem( nextItem );
+ } else {
+ this.chooseItem( nextItem );
+ }
+ nextItem.scrollElementIntoView();
+ }
+
+ if ( handled ) {
+ // Can't just return false, because e is not always a jQuery event
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ }
+};
+
+/**
+ * Bind key down listener.
+ *
+ * @protected
+ */
+OO.ui.SelectWidget.prototype.bindKeyDownListener = function () {
+ this.getElementWindow().addEventListener( 'keydown', this.onKeyDownHandler, true );
+};
+
+/**
+ * Unbind key down listener.
+ *
+ * @protected
+ */
+OO.ui.SelectWidget.prototype.unbindKeyDownListener = function () {
+ this.getElementWindow().removeEventListener( 'keydown', this.onKeyDownHandler, true );
+};
+
+/**
+ * Get the closest item to a jQuery.Event.
+ *
+ * @private
+ * @param {jQuery.Event} e
+ * @return {OO.ui.OptionWidget|null} Outline item widget, `null` if none was found
+ */
+OO.ui.SelectWidget.prototype.getTargetItem = function ( e ) {
+ return $( e.target ).closest( '.oo-ui-optionWidget' ).data( 'oo-ui-optionWidget' ) || null;
+};
+
+/**
+ * Get selected item.
+ *
+ * @return {OO.ui.OptionWidget|null} Selected item, `null` if no item is selected
+ */
+OO.ui.SelectWidget.prototype.getSelectedItem = function () {
+ var i, len;
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ if ( this.items[ i ].isSelected() ) {
+ return this.items[ i ];
+ }
+ }
+ return null;
+};
+
+/**
+ * Get highlighted item.
+ *
+ * @return {OO.ui.OptionWidget|null} Highlighted item, `null` if no item is highlighted
+ */
+OO.ui.SelectWidget.prototype.getHighlightedItem = function () {
+ var i, len;
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ if ( this.items[ i ].isHighlighted() ) {
+ return this.items[ i ];
+ }
+ }
+ return null;
+};
+
+/**
+ * Toggle pressed state.
+ *
+ * Press is a state that occurs when a user mouses down on an item, but
+ * has not yet let go of the mouse. The item may appear selected, but it will not be selected
+ * until the user releases the mouse.
+ *
+ * @param {boolean} pressed An option is being pressed
+ */
+OO.ui.SelectWidget.prototype.togglePressed = function ( pressed ) {
+ if ( pressed === undefined ) {
+ pressed = !this.pressed;
+ }
+ if ( pressed !== this.pressed ) {
+ this.$element
+ .toggleClass( 'oo-ui-selectWidget-pressed', pressed )
+ .toggleClass( 'oo-ui-selectWidget-depressed', !pressed );
+ this.pressed = pressed;
+ }
+};
+
+/**
+ * Highlight an option. If the `item` param is omitted, no options will be highlighted
+ * and any existing highlight will be removed. The highlight is mutually exclusive.
+ *
+ * @param {OO.ui.OptionWidget} [item] Item to highlight, omit for no highlight
+ * @fires highlight
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.highlightItem = function ( item ) {
+ var i, len, highlighted,
+ changed = false;
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ highlighted = this.items[ i ] === item;
+ if ( this.items[ i ].isHighlighted() !== highlighted ) {
+ this.items[ i ].setHighlighted( highlighted );
+ changed = true;
+ }
+ }
+ if ( changed ) {
+ this.emit( 'highlight', item );
+ }
+
+ return this;
+};
+
+/**
+ * Programmatically select an option by its data. If the `data` parameter is omitted,
+ * or if the item does not exist, all options will be deselected.
+ *
+ * @param {Object|string} [data] Value of the item to select, omit to deselect all
+ * @fires select
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.selectItemByData = function ( data ) {
+ var itemFromData = this.getItemFromData( data );
+ if ( data === undefined || !itemFromData ) {
+ return this.selectItem();
+ }
+ return this.selectItem( itemFromData );
+};
+
+/**
+ * Programmatically select an option by its reference. If the `item` parameter is omitted,
+ * all options will be deselected.
+ *
+ * @param {OO.ui.OptionWidget} [item] Item to select, omit to deselect all
+ * @fires select
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.selectItem = function ( item ) {
+ var i, len, selected,
+ changed = false;
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ selected = this.items[ i ] === item;
+ if ( this.items[ i ].isSelected() !== selected ) {
+ this.items[ i ].setSelected( selected );
+ changed = true;
+ }
+ }
+ if ( changed ) {
+ this.emit( 'select', item );
+ }
+
+ return this;
+};
+
+/**
+ * Press an item.
+ *
+ * Press is a state that occurs when a user mouses down on an item, but has not
+ * yet let go of the mouse. The item may appear selected, but it will not be selected until the user
+ * releases the mouse.
+ *
+ * @param {OO.ui.OptionWidget} [item] Item to press, omit to depress all
+ * @fires press
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.pressItem = function ( item ) {
+ var i, len, pressed,
+ changed = false;
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ pressed = this.items[ i ] === item;
+ if ( this.items[ i ].isPressed() !== pressed ) {
+ this.items[ i ].setPressed( pressed );
+ changed = true;
+ }
+ }
+ if ( changed ) {
+ this.emit( 'press', item );
+ }
+
+ return this;
+};
+
+/**
+ * Choose an item.
+ *
+ * Note that ‘choose’ should never be modified programmatically. A user can choose
+ * an option with the keyboard or mouse and it becomes selected. To select an item programmatically,
+ * use the #selectItem method.
+ *
+ * This method is identical to #selectItem, but may vary in subclasses that take additional action
+ * when users choose an item with the keyboard or mouse.
+ *
+ * @param {OO.ui.OptionWidget} item Item to choose
+ * @fires choose
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.chooseItem = function ( item ) {
+ this.selectItem( item );
+ this.emit( 'choose', item );
+
+ return this;
+};
+
+/**
+ * Get an option by its position relative to the specified item (or to the start of the option array,
+ * if item is `null`). The direction in which to search through the option array is specified with a
+ * number: -1 for reverse (the default) or 1 for forward. The method will return an option, or
+ * `null` if there are no options in the array.
+ *
+ * @param {OO.ui.OptionWidget|null} item Item to describe the start position, or `null` to start at the beginning of the array.
+ * @param {number} direction Direction to move in: -1 to move backward, 1 to move forward
+ * @return {OO.ui.OptionWidget|null} Item at position, `null` if there are no items in the select
+ */
+OO.ui.SelectWidget.prototype.getRelativeSelectableItem = function ( item, direction ) {
+ var currentIndex, nextIndex, i,
+ increase = direction > 0 ? 1 : -1,
+ len = this.items.length;
+
+ if ( item instanceof OO.ui.OptionWidget ) {
+ currentIndex = $.inArray( item, this.items );
+ nextIndex = ( currentIndex + increase + len ) % len;
+ } else {
+ // If no item is selected and moving forward, start at the beginning.
+ // If moving backward, start at the end.
+ nextIndex = direction > 0 ? 0 : len - 1;
+ }
+
+ for ( i = 0; i < len; i++ ) {
+ item = this.items[ nextIndex ];
+ if ( item instanceof OO.ui.OptionWidget && item.isSelectable() ) {
+ return item;
+ }
+ nextIndex = ( nextIndex + increase + len ) % len;
+ }
+ return null;
+};
+
+/**
+ * Get the next selectable item or `null` if there are no selectable items.
+ * Disabled options and menu-section markers and breaks are not selectable.
+ *
+ * @return {OO.ui.OptionWidget|null} Item, `null` if there aren't any selectable items
+ */
+OO.ui.SelectWidget.prototype.getFirstSelectableItem = function () {
+ var i, len, item;
+
+ for ( i = 0, len = this.items.length; i < len; i++ ) {
+ item = this.items[ i ];
+ if ( item instanceof OO.ui.OptionWidget && item.isSelectable() ) {
+ return item;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Add an array of options to the select. Optionally, an index number can be used to
+ * specify an insertion point.
+ *
+ * @param {OO.ui.OptionWidget[]} items Items to add
+ * @param {number} [index] Index to insert items after
+ * @fires add
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.addItems = function ( items, index ) {
+ // Mixin method
+ OO.ui.GroupWidget.prototype.addItems.call( this, items, index );
+
+ // Always provide an index, even if it was omitted
+ this.emit( 'add', items, index === undefined ? this.items.length - items.length - 1 : index );
+
+ return this;
+};
+
+/**
+ * Remove the specified array of options from the select. Options will be detached
+ * from the DOM, not removed, so they can be reused later. To remove all options from
+ * the select, you may wish to use the #clearItems method instead.
+ *
+ * @param {OO.ui.OptionWidget[]} items Items to remove
+ * @fires remove
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.removeItems = function ( items ) {
+ var i, len, item;
+
+ // Deselect items being removed
+ for ( i = 0, len = items.length; i < len; i++ ) {
+ item = items[ i ];
+ if ( item.isSelected() ) {
+ this.selectItem( null );
+ }
+ }
+
+ // Mixin method
+ OO.ui.GroupWidget.prototype.removeItems.call( this, items );
+
+ this.emit( 'remove', items );
+
+ return this;
+};
+
+/**
+ * Clear all options from the select. Options will be detached from the DOM, not removed,
+ * so that they can be reused later. To remove a subset of options from the select, use
+ * the #removeItems method.
+ *
+ * @fires remove
+ * @chainable
+ */
+OO.ui.SelectWidget.prototype.clearItems = function () {
+ var items = this.items.slice();
+
+ // Mixin method
+ OO.ui.GroupWidget.prototype.clearItems.call( this );
+
+ // Clear selection
+ this.selectItem( null );
+
+ this.emit( 'remove', items );
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/TabOptionWidget.js b/vendor/oojs/oojs-ui/src/widgets/TabOptionWidget.js
new file mode 100644
index 00000000..08f5b30d
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/TabOptionWidget.js
@@ -0,0 +1,31 @@
+/**
+ * TabOptionWidget is an item in a {@link OO.ui.TabSelectWidget TabSelectWidget}.
+ *
+ * Currently, this class is only used by {@link OO.ui.IndexLayout index layouts}, which contain
+ * {@link OO.ui.CardLayout card layouts}. See {@link OO.ui.IndexLayout IndexLayout}
+ * for an example.
+ *
+ * @class
+ * @extends OO.ui.OptionWidget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.TabOptionWidget = function OoUiTabOptionWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.TabOptionWidget.super.call( this, config );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-tabOptionWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.TabOptionWidget, OO.ui.OptionWidget );
+
+/* Static Properties */
+
+OO.ui.TabOptionWidget.static.highlightable = false;
diff --git a/vendor/oojs/oojs-ui/src/widgets/TabSelectWidget.js b/vendor/oojs/oojs-ui/src/widgets/TabSelectWidget.js
new file mode 100644
index 00000000..ced3218b
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/TabSelectWidget.js
@@ -0,0 +1,33 @@
+/**
+ * TabSelectWidget is a list that contains {@link OO.ui.TabOptionWidget tab options}
+ *
+ * ####Currently, this class is only used by {@link OO.ui.IndexLayout index layouts}.####
+ *
+ * @class
+ * @extends OO.ui.SelectWidget
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ */
+OO.ui.TabSelectWidget = function OoUiTabSelectWidget( config ) {
+ // Parent constructor
+ OO.ui.TabSelectWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.TabIndexedElement.call( this, config );
+
+ // Events
+ this.$element.on( {
+ focus: this.bindKeyDownListener.bind( this ),
+ blur: this.unbindKeyDownListener.bind( this )
+ } );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-tabSelectWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.TabSelectWidget, OO.ui.SelectWidget );
+OO.mixinClass( OO.ui.TabSelectWidget, OO.ui.TabIndexedElement );
diff --git a/vendor/oojs/oojs-ui/src/widgets/TextInputMenuSelectWidget.js b/vendor/oojs/oojs-ui/src/widgets/TextInputMenuSelectWidget.js
new file mode 100644
index 00000000..6b971e81
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/TextInputMenuSelectWidget.js
@@ -0,0 +1,103 @@
+/**
+ * TextInputMenuSelectWidget is a menu that is specially designed to be positioned beneath
+ * a {@link OO.ui.TextInputWidget text input} field. The menu's position is automatically
+ * calculated and maintained when the menu is toggled or the window is resized.
+ * See OO.ui.ComboBoxWidget for an example of a widget that uses this class.
+ *
+ * @class
+ * @extends OO.ui.MenuSelectWidget
+ *
+ * @constructor
+ * @param {OO.ui.TextInputWidget} inputWidget Text input widget to provide menu for
+ * @param {Object} [config] Configuration options
+ * @cfg {jQuery} [$container=input.$element] Element to render menu under
+ */
+OO.ui.TextInputMenuSelectWidget = function OoUiTextInputMenuSelectWidget( inputWidget, config ) {
+ // Allow passing positional parameters inside the config object
+ if ( OO.isPlainObject( inputWidget ) && config === undefined ) {
+ config = inputWidget;
+ inputWidget = config.inputWidget;
+ }
+
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.TextInputMenuSelectWidget.super.call( this, config );
+
+ // Properties
+ this.inputWidget = inputWidget;
+ this.$container = config.$container || this.inputWidget.$element;
+ this.onWindowResizeHandler = this.onWindowResize.bind( this );
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-textInputMenuSelectWidget' );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.TextInputMenuSelectWidget, OO.ui.MenuSelectWidget );
+
+/* Methods */
+
+/**
+ * Handle window resize event.
+ *
+ * @private
+ * @param {jQuery.Event} e Window resize event
+ */
+OO.ui.TextInputMenuSelectWidget.prototype.onWindowResize = function () {
+ this.position();
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.TextInputMenuSelectWidget.prototype.toggle = function ( visible ) {
+ visible = visible === undefined ? !this.isVisible() : !!visible;
+
+ var change = visible !== this.isVisible();
+
+ if ( change && visible ) {
+ // Make sure the width is set before the parent method runs.
+ // After this we have to call this.position(); again to actually
+ // position ourselves correctly.
+ this.position();
+ }
+
+ // Parent method
+ OO.ui.TextInputMenuSelectWidget.super.prototype.toggle.call( this, visible );
+
+ if ( change ) {
+ if ( this.isVisible() ) {
+ this.position();
+ $( this.getElementWindow() ).on( 'resize', this.onWindowResizeHandler );
+ } else {
+ $( this.getElementWindow() ).off( 'resize', this.onWindowResizeHandler );
+ }
+ }
+
+ return this;
+};
+
+/**
+ * Position the menu.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.TextInputMenuSelectWidget.prototype.position = function () {
+ var $container = this.$container,
+ pos = OO.ui.Element.static.getRelativePosition( $container, this.$element.offsetParent() );
+
+ // Position under input
+ pos.top += $container.height();
+ this.$element.css( pos );
+
+ // Set width
+ this.setIdealSize( $container.width() );
+ // We updated the position, so re-evaluate the clipping state
+ this.clip();
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js b/vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js
new file mode 100644
index 00000000..1ba26641
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/TextInputWidget.js
@@ -0,0 +1,535 @@
+/**
+ * TextInputWidgets, like HTML text inputs, can be configured with options that customize the
+ * size of the field as well as its presentation. In addition, these widgets can be configured
+ * with {@link OO.ui.IconElement icons}, {@link OO.ui.IndicatorElement indicators}, an optional
+ * validation-pattern (used to determine if an input value is valid or not) and an input filter,
+ * which modifies incoming values rather than validating them.
+ * Please see the [OOjs UI documentation on MediaWiki] [1] for more information and examples.
+ *
+ * This widget can be used inside a HTML form, such as a OO.ui.FormLayout.
+ *
+ * @example
+ * // Example of a text input widget
+ * var textInput = new OO.ui.TextInputWidget( {
+ * value: 'Text input'
+ * } )
+ * $( 'body' ).append( textInput.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Inputs
+ *
+ * @class
+ * @extends OO.ui.InputWidget
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.PendingElement
+ * @mixins OO.ui.LabelElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {string} [type='text'] The value of the HTML `type` attribute
+ * @cfg {string} [placeholder] Placeholder text
+ * @cfg {boolean} [autofocus=false] Use an HTML `autofocus` attribute to
+ * instruct the browser to focus this widget.
+ * @cfg {boolean} [readOnly=false] Prevent changes to the value of the text input.
+ * @cfg {number} [maxLength] Maximum number of characters allowed in the input.
+ * @cfg {boolean} [multiline=false] Allow multiple lines of text
+ * @cfg {boolean} [autosize=false] Automatically resize the text input to fit its content.
+ * Use the #maxRows config to specify a maximum number of displayed rows.
+ * @cfg {boolean} [maxRows=10] Maximum number of rows to display when #autosize is set to true.
+ * @cfg {string} [labelPosition='after'] The position of the inline label relative to that of
+ * the value or placeholder text: `'before'` or `'after'`
+ * @cfg {boolean} [required=false] Mark the field as required
+ * @cfg {RegExp|Function|string} [validate] Validation pattern: when string, a symbolic name of a
+ * pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer'
+ * (the value must contain only numbers); when RegExp, a regular expression that must match the
+ * value for it to be considered valid; when Function, a function receiving the value as parameter
+ * that must return true, or promise resolving to true, for it to be considered valid.
+ */
+OO.ui.TextInputWidget = function OoUiTextInputWidget( config ) {
+ // Configuration initialization
+ config = $.extend( {
+ type: 'text',
+ labelPosition: 'after',
+ maxRows: 10
+ }, config );
+
+ // Parent constructor
+ OO.ui.TextInputWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.IconElement.call( this, config );
+ OO.ui.IndicatorElement.call( this, config );
+ OO.ui.PendingElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+
+ // Properties
+ this.readOnly = false;
+ this.multiline = !!config.multiline;
+ this.autosize = !!config.autosize;
+ this.maxRows = config.maxRows;
+ this.validate = null;
+
+ // Clone for resizing
+ if ( this.autosize ) {
+ this.$clone = this.$input
+ .clone()
+ .insertAfter( this.$input )
+ .attr( 'aria-hidden', 'true' )
+ .addClass( 'oo-ui-element-hidden' );
+ }
+
+ this.setValidation( config.validate );
+ this.setLabelPosition( config.labelPosition );
+
+ // Events
+ this.$input.on( {
+ keypress: this.onKeyPress.bind( this ),
+ blur: this.onBlur.bind( this )
+ } );
+ this.$input.one( {
+ focus: this.onElementAttach.bind( this )
+ } );
+ this.$icon.on( 'mousedown', this.onIconMouseDown.bind( this ) );
+ this.$indicator.on( 'mousedown', this.onIndicatorMouseDown.bind( this ) );
+ this.on( 'labelChange', this.updatePosition.bind( this ) );
+ this.connect( this, { change: 'onChange' } );
+
+ // Initialization
+ this.$element
+ .addClass( 'oo-ui-textInputWidget' )
+ .append( this.$icon, this.$indicator );
+ this.setReadOnly( !!config.readOnly );
+ if ( config.placeholder ) {
+ this.$input.attr( 'placeholder', config.placeholder );
+ }
+ if ( config.maxLength !== undefined ) {
+ this.$input.attr( 'maxlength', config.maxLength );
+ }
+ if ( config.autofocus ) {
+ this.$input.attr( 'autofocus', 'autofocus' );
+ }
+ if ( config.required ) {
+ this.$input.attr( 'required', 'required' );
+ this.$input.attr( 'aria-required', 'true' );
+ }
+ if ( this.label || config.autosize ) {
+ this.installParentChangeDetector();
+ }
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.TextInputWidget, OO.ui.InputWidget );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IconElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.PendingElement );
+OO.mixinClass( OO.ui.TextInputWidget, OO.ui.LabelElement );
+
+/* Static properties */
+
+OO.ui.TextInputWidget.static.validationPatterns = {
+ 'non-empty': /.+/,
+ integer: /^\d+$/
+};
+
+/* Events */
+
+/**
+ * An `enter` event is emitted when the user presses 'enter' inside the text box.
+ *
+ * Not emitted if the input is multiline.
+ *
+ * @event enter
+ */
+
+/* Methods */
+
+/**
+ * Handle icon mouse down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse down event
+ * @fires icon
+ */
+OO.ui.TextInputWidget.prototype.onIconMouseDown = function ( e ) {
+ if ( e.which === 1 ) {
+ this.$input[ 0 ].focus();
+ return false;
+ }
+};
+
+/**
+ * Handle indicator mouse down events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse down event
+ * @fires indicator
+ */
+OO.ui.TextInputWidget.prototype.onIndicatorMouseDown = function ( e ) {
+ if ( e.which === 1 ) {
+ this.$input[ 0 ].focus();
+ return false;
+ }
+};
+
+/**
+ * Handle key press events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ * @fires enter If enter key is pressed and input is not multiline
+ */
+OO.ui.TextInputWidget.prototype.onKeyPress = function ( e ) {
+ if ( e.which === OO.ui.Keys.ENTER && !this.multiline ) {
+ this.emit( 'enter', e );
+ }
+};
+
+/**
+ * Handle blur events.
+ *
+ * @private
+ * @param {jQuery.Event} e Blur event
+ */
+OO.ui.TextInputWidget.prototype.onBlur = function () {
+ this.setValidityFlag();
+};
+
+/**
+ * Handle element attach events.
+ *
+ * @private
+ * @param {jQuery.Event} e Element attach event
+ */
+OO.ui.TextInputWidget.prototype.onElementAttach = function () {
+ // Any previously calculated size is now probably invalid if we reattached elsewhere
+ this.valCache = null;
+ this.adjustSize();
+ this.positionLabel();
+};
+
+/**
+ * Handle change events.
+ *
+ * @param {string} value
+ * @private
+ */
+OO.ui.TextInputWidget.prototype.onChange = function () {
+ this.setValidityFlag();
+ this.adjustSize();
+};
+
+/**
+ * Check if the input is {@link #readOnly read-only}.
+ *
+ * @return {boolean}
+ */
+OO.ui.TextInputWidget.prototype.isReadOnly = function () {
+ return this.readOnly;
+};
+
+/**
+ * Set the {@link #readOnly read-only} state of the input.
+ *
+ * @param {boolean} state Make input read-only
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.setReadOnly = function ( state ) {
+ this.readOnly = !!state;
+ this.$input.prop( 'readOnly', this.readOnly );
+ return this;
+};
+
+/**
+ * Support function for making #onElementAttach work across browsers.
+ *
+ * This whole function could be replaced with one line of code using the DOMNodeInsertedIntoDocument
+ * event, but it's not supported by Firefox and allegedly deprecated, so we only use it as fallback.
+ *
+ * Due to MutationObserver performance woes, #onElementAttach is only somewhat reliably called the
+ * first time that the element gets attached to the documented.
+ */
+OO.ui.TextInputWidget.prototype.installParentChangeDetector = function () {
+ var mutationObserver, onRemove, topmostNode, fakeParentNode,
+ MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
+ widget = this;
+
+ if ( MutationObserver ) {
+ // The new way. If only it wasn't so ugly.
+
+ if ( this.$element.closest( 'html' ).length ) {
+ // Widget is attached already, do nothing. This breaks the functionality of this function when
+ // the widget is detached and reattached. Alas, doing this correctly with MutationObserver
+ // would require observation of the whole document, which would hurt performance of other,
+ // more important code.
+ return;
+ }
+
+ // Find topmost node in the tree
+ topmostNode = this.$element[0];
+ while ( topmostNode.parentNode ) {
+ topmostNode = topmostNode.parentNode;
+ }
+
+ // We have no way to detect the $element being attached somewhere without observing the entire
+ // DOM with subtree modifications, which would hurt performance. So we cheat: we hook to the
+ // parent node of $element, and instead detect when $element is removed from it (and thus
+ // probably attached somewhere else). If there is no parent, we create a "fake" one. If it
+ // doesn't get attached, we end up back here and create the parent.
+
+ mutationObserver = new MutationObserver( function ( mutations ) {
+ var i, j, removedNodes;
+ for ( i = 0; i < mutations.length; i++ ) {
+ removedNodes = mutations[ i ].removedNodes;
+ for ( j = 0; j < removedNodes.length; j++ ) {
+ if ( removedNodes[ j ] === topmostNode ) {
+ setTimeout( onRemove, 0 );
+ return;
+ }
+ }
+ }
+ } );
+
+ onRemove = function () {
+ // If the node was attached somewhere else, report it
+ if ( widget.$element.closest( 'html' ).length ) {
+ widget.onElementAttach();
+ }
+ mutationObserver.disconnect();
+ widget.installParentChangeDetector();
+ };
+
+ // Create a fake parent and observe it
+ fakeParentNode = $( '<div>' ).append( this.$element )[0];
+ mutationObserver.observe( fakeParentNode, { childList: true } );
+ } else {
+ // Using the DOMNodeInsertedIntoDocument event is much nicer and less magical, and works for
+ // detachment and reattachment, but it's not supported by Firefox and allegedly deprecated.
+ this.$element.on( 'DOMNodeInsertedIntoDocument', this.onElementAttach.bind( this ) );
+ }
+};
+
+/**
+ * Automatically adjust the size of the text input.
+ *
+ * This only affects #multiline inputs that are {@link #autosize autosized}.
+ *
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.adjustSize = function () {
+ var scrollHeight, innerHeight, outerHeight, maxInnerHeight, measurementError, idealHeight;
+
+ if ( this.multiline && this.autosize && this.$input.val() !== this.valCache ) {
+ this.$clone
+ .val( this.$input.val() )
+ .attr( 'rows', '' )
+ // Set inline height property to 0 to measure scroll height
+ .css( 'height', 0 );
+
+ this.$clone.removeClass( 'oo-ui-element-hidden' );
+
+ this.valCache = this.$input.val();
+
+ scrollHeight = this.$clone[ 0 ].scrollHeight;
+
+ // Remove inline height property to measure natural heights
+ this.$clone.css( 'height', '' );
+ innerHeight = this.$clone.innerHeight();
+ outerHeight = this.$clone.outerHeight();
+
+ // Measure max rows height
+ this.$clone
+ .attr( 'rows', this.maxRows )
+ .css( 'height', 'auto' )
+ .val( '' );
+ maxInnerHeight = this.$clone.innerHeight();
+
+ // Difference between reported innerHeight and scrollHeight with no scrollbars present
+ // Equals 1 on Blink-based browsers and 0 everywhere else
+ measurementError = maxInnerHeight - this.$clone[ 0 ].scrollHeight;
+ idealHeight = Math.min( maxInnerHeight, scrollHeight + measurementError );
+
+ this.$clone.addClass( 'oo-ui-element-hidden' );
+
+ // Only apply inline height when expansion beyond natural height is needed
+ if ( idealHeight > innerHeight ) {
+ // Use the difference between the inner and outer height as a buffer
+ this.$input.css( 'height', idealHeight + ( outerHeight - innerHeight ) );
+ } else {
+ this.$input.css( 'height', '' );
+ }
+ }
+ return this;
+};
+
+/**
+ * @inheritdoc
+ * @private
+ */
+OO.ui.TextInputWidget.prototype.getInputElement = function ( config ) {
+ return config.multiline ? $( '<textarea>' ) : $( '<input type="' + config.type + '" />' );
+};
+
+/**
+ * Check if the input supports multiple lines.
+ *
+ * @return {boolean}
+ */
+OO.ui.TextInputWidget.prototype.isMultiline = function () {
+ return !!this.multiline;
+};
+
+/**
+ * Check if the input automatically adjusts its size.
+ *
+ * @return {boolean}
+ */
+OO.ui.TextInputWidget.prototype.isAutosizing = function () {
+ return !!this.autosize;
+};
+
+/**
+ * Select the entire text of the input.
+ *
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.select = function () {
+ this.$input.select();
+ return this;
+};
+
+/**
+ * Set the validation pattern.
+ *
+ * The validation pattern is either a regular expression, a function, or the symbolic name of a
+ * pattern defined by the class: 'non-empty' (the value cannot be an empty string) or 'integer' (the
+ * value must contain only numbers).
+ *
+ * @param {RegExp|Function|string|null} validate Regular expression, function, or the symbolic name
+ * of a pattern (either ‘integer’ or ‘non-empty’) defined by the class.
+ */
+OO.ui.TextInputWidget.prototype.setValidation = function ( validate ) {
+ if ( validate instanceof RegExp || validate instanceof Function ) {
+ this.validate = validate;
+ } else {
+ this.validate = this.constructor.static.validationPatterns[ validate ] || /.*/;
+ }
+};
+
+/**
+ * Sets the 'invalid' flag appropriately.
+ *
+ * @param {boolean} [isValid] Optionally override validation result
+ */
+OO.ui.TextInputWidget.prototype.setValidityFlag = function ( isValid ) {
+ var widget = this,
+ setFlag = function ( valid ) {
+ if ( !valid ) {
+ widget.$input.attr( 'aria-invalid', 'true' );
+ } else {
+ widget.$input.removeAttr( 'aria-invalid' );
+ }
+ widget.setFlags( { invalid: !valid } );
+ };
+
+ if ( isValid !== undefined ) {
+ setFlag( isValid );
+ } else {
+ this.isValid().done( setFlag );
+ }
+};
+
+/**
+ * Check if a value is valid.
+ *
+ * This method returns a promise that resolves with a boolean `true` if the current value is
+ * considered valid according to the supplied {@link #validate validation pattern}.
+ *
+ * @return {jQuery.Promise} A promise that resolves to a boolean `true` if the value is valid.
+ */
+OO.ui.TextInputWidget.prototype.isValid = function () {
+ if ( this.validate instanceof Function ) {
+ var result = this.validate( this.getValue() );
+ if ( $.isFunction( result.promise ) ) {
+ return result.promise();
+ } else {
+ return $.Deferred().resolve( !!result ).promise();
+ }
+ } else {
+ return $.Deferred().resolve( !!this.getValue().match( this.validate ) ).promise();
+ }
+};
+
+/**
+ * Set the position of the inline label relative to that of the value: `‘before’` or `‘after’`.
+ *
+ * @param {string} labelPosition Label position, 'before' or 'after'
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.setLabelPosition = function ( labelPosition ) {
+ this.labelPosition = labelPosition;
+ this.updatePosition();
+ return this;
+};
+
+/**
+ * Deprecated alias of #setLabelPosition
+ *
+ * @deprecated Use setLabelPosition instead.
+ */
+OO.ui.TextInputWidget.prototype.setPosition =
+ OO.ui.TextInputWidget.prototype.setLabelPosition;
+
+/**
+ * Update the position of the inline label.
+ *
+ * This method is called by #setLabelPosition, and can also be called on its own if
+ * something causes the label to be mispositioned.
+ *
+ *
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.updatePosition = function () {
+ var after = this.labelPosition === 'after';
+
+ this.$element
+ .toggleClass( 'oo-ui-textInputWidget-labelPosition-after', !!this.label && after )
+ .toggleClass( 'oo-ui-textInputWidget-labelPosition-before', !!this.label && !after );
+
+ if ( this.label ) {
+ this.positionLabel();
+ }
+
+ return this;
+};
+
+/**
+ * Position the label by setting the correct padding on the input.
+ *
+ * @private
+ * @chainable
+ */
+OO.ui.TextInputWidget.prototype.positionLabel = function () {
+ // Clear old values
+ this.$input
+ // Clear old values if present
+ .css( {
+ 'padding-right': '',
+ 'padding-left': ''
+ } );
+
+ if ( this.label ) {
+ this.$element.append( this.$label );
+ } else {
+ this.$label.detach();
+ return;
+ }
+
+ var after = this.labelPosition === 'after',
+ rtl = this.$element.css( 'direction' ) === 'rtl',
+ property = after === rtl ? 'padding-left' : 'padding-right';
+
+ this.$input.css( property, this.$label.outerWidth( true ) );
+
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ToggleButtonWidget.js b/vendor/oojs/oojs-ui/src/widgets/ToggleButtonWidget.js
new file mode 100644
index 00000000..bb36f083
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ToggleButtonWidget.js
@@ -0,0 +1,114 @@
+/**
+ * ToggleButtons are buttons that have a state (‘on’ or ‘off’) that is represented by a
+ * Boolean value. Like other {@link OO.ui.ButtonWidget buttons}, toggle buttons can be
+ * configured with {@link OO.ui.IconElement icons}, {@link OO.ui.IndicatorElement indicators},
+ * {@link OO.ui.TitledElement titles}, {@link OO.ui.FlaggedElement styling flags},
+ * and {@link OO.ui.LabelElement labels}. Please see
+ * the [OOjs UI documentation][1] on MediaWiki for more information.
+ *
+ * @example
+ * // Toggle buttons in the 'off' and 'on' state.
+ * var toggleButton1 = new OO.ui.ToggleButtonWidget( {
+ * label: 'Toggle Button off'
+ * } );
+ * var toggleButton2 = new OO.ui.ToggleButtonWidget( {
+ * label: 'Toggle Button on',
+ * value: true
+ * } );
+ * // Append the buttons to the DOM.
+ * $( 'body' ).append( toggleButton1.$element, toggleButton2.$element );
+ *
+ * [1]: https://www.mediawiki.org/wiki/OOjs_UI/Widgets/Buttons_and_Switches#Toggle_buttons
+ *
+ * @class
+ * @extends OO.ui.ToggleWidget
+ * @mixins OO.ui.ButtonElement
+ * @mixins OO.ui.IconElement
+ * @mixins OO.ui.IndicatorElement
+ * @mixins OO.ui.LabelElement
+ * @mixins OO.ui.TitledElement
+ * @mixins OO.ui.FlaggedElement
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [value=false] The toggle button’s initial on/off
+ * state. By default, the button is in the 'off' state.
+ */
+OO.ui.ToggleButtonWidget = function OoUiToggleButtonWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ToggleButtonWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.ButtonElement.call( this, config );
+ OO.ui.IconElement.call( this, config );
+ OO.ui.IndicatorElement.call( this, config );
+ OO.ui.LabelElement.call( this, config );
+ OO.ui.TitledElement.call( this, $.extend( {}, config, { $titled: this.$button } ) );
+ OO.ui.FlaggedElement.call( this, config );
+ OO.ui.TabIndexedElement.call( this, $.extend( {}, config, { $tabIndexed: this.$button } ) );
+
+ // Events
+ this.connect( this, { click: 'onAction' } );
+
+ // Initialization
+ this.$button.append( this.$icon, this.$label, this.$indicator );
+ this.$element
+ .addClass( 'oo-ui-toggleButtonWidget' )
+ .append( this.$button );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ToggleButtonWidget, OO.ui.ToggleWidget );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.ButtonElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.IconElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.IndicatorElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.LabelElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.TitledElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.FlaggedElement );
+OO.mixinClass( OO.ui.ToggleButtonWidget, OO.ui.TabIndexedElement );
+
+/* Methods */
+
+/**
+ * Handle the button action being triggered.
+ *
+ * @private
+ */
+OO.ui.ToggleButtonWidget.prototype.onAction = function () {
+ this.setValue( !this.value );
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ToggleButtonWidget.prototype.setValue = function ( value ) {
+ value = !!value;
+ if ( value !== this.value ) {
+ // Might be called from parent constructor before ButtonElement constructor
+ if ( this.$button ) {
+ this.$button.attr( 'aria-pressed', value.toString() );
+ }
+ this.setActive( value );
+ }
+
+ // Parent method
+ OO.ui.ToggleButtonWidget.super.prototype.setValue.call( this, value );
+
+ return this;
+};
+
+/**
+ * @inheritdoc
+ */
+OO.ui.ToggleButtonWidget.prototype.setButtonElement = function ( $button ) {
+ if ( this.$button ) {
+ this.$button.removeAttr( 'aria-pressed' );
+ }
+ OO.ui.ButtonElement.prototype.setButtonElement.call( this, $button );
+ this.$button.attr( 'aria-pressed', this.value.toString() );
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ToggleSwitchWidget.js b/vendor/oojs/oojs-ui/src/widgets/ToggleSwitchWidget.js
new file mode 100644
index 00000000..94f0f996
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ToggleSwitchWidget.js
@@ -0,0 +1,92 @@
+/**
+ * ToggleSwitches are switches that slide on and off. Their state is represented by a Boolean
+ * value (`true` for ‘on’, and `false` otherwise, the default). The ‘off’ state is represented
+ * visually by a slider in the leftmost position.
+ *
+ * @example
+ * // Toggle switches in the 'off' and 'on' position.
+ * var toggleSwitch1 = new OO.ui.ToggleSwitchWidget();
+ * var toggleSwitch2 = new OO.ui.ToggleSwitchWidget( {
+ * value: true
+ * } );
+ *
+ * // Create a FieldsetLayout to layout and label switches
+ * var fieldset = new OO.ui.FieldsetLayout( {
+ * label: 'Toggle switches'
+ * } );
+ * fieldset.addItems( [
+ * new OO.ui.FieldLayout( toggleSwitch1, { label: 'Off', align: 'top' } ),
+ * new OO.ui.FieldLayout( toggleSwitch2, { label: 'On', align: 'top' } )
+ * ] );
+ * $( 'body' ).append( fieldset.$element );
+ *
+ * @class
+ * @extends OO.ui.ToggleWidget
+ * @mixins OO.ui.TabIndexedElement
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [value=false] The toggle switch’s initial on/off state.
+ * By default, the toggle switch is in the 'off' position.
+ */
+OO.ui.ToggleSwitchWidget = function OoUiToggleSwitchWidget( config ) {
+ // Parent constructor
+ OO.ui.ToggleSwitchWidget.super.call( this, config );
+
+ // Mixin constructors
+ OO.ui.TabIndexedElement.call( this, config );
+
+ // Properties
+ this.dragging = false;
+ this.dragStart = null;
+ this.sliding = false;
+ this.$glow = $( '<span>' );
+ this.$grip = $( '<span>' );
+
+ // Events
+ this.$element.on( {
+ click: this.onClick.bind( this ),
+ keypress: this.onKeyPress.bind( this )
+ } );
+
+ // Initialization
+ this.$glow.addClass( 'oo-ui-toggleSwitchWidget-glow' );
+ this.$grip.addClass( 'oo-ui-toggleSwitchWidget-grip' );
+ this.$element
+ .addClass( 'oo-ui-toggleSwitchWidget' )
+ .attr( 'role', 'checkbox' )
+ .append( this.$glow, this.$grip );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ToggleSwitchWidget, OO.ui.ToggleWidget );
+OO.mixinClass( OO.ui.ToggleSwitchWidget, OO.ui.TabIndexedElement );
+
+/* Methods */
+
+/**
+ * Handle mouse click events.
+ *
+ * @private
+ * @param {jQuery.Event} e Mouse click event
+ */
+OO.ui.ToggleSwitchWidget.prototype.onClick = function ( e ) {
+ if ( !this.isDisabled() && e.which === 1 ) {
+ this.setValue( !this.value );
+ }
+ return false;
+};
+
+/**
+ * Handle key press events.
+ *
+ * @private
+ * @param {jQuery.Event} e Key press event
+ */
+OO.ui.ToggleSwitchWidget.prototype.onKeyPress = function ( e ) {
+ if ( !this.isDisabled() && ( e.which === OO.ui.Keys.SPACE || e.which === OO.ui.Keys.ENTER ) ) {
+ this.setValue( !this.value );
+ return false;
+ }
+};
diff --git a/vendor/oojs/oojs-ui/src/widgets/ToggleWidget.js b/vendor/oojs/oojs-ui/src/widgets/ToggleWidget.js
new file mode 100644
index 00000000..16d6ba50
--- /dev/null
+++ b/vendor/oojs/oojs-ui/src/widgets/ToggleWidget.js
@@ -0,0 +1,71 @@
+/**
+ * ToggleWidget implements basic behavior of widgets with an on/off state.
+ * Please see OO.ui.ToggleButtonWidget and OO.ui.ToggleSwitchWidget for examples.
+ *
+ * @abstract
+ * @class
+ * @extends OO.ui.Widget
+ *
+ * @constructor
+ * @param {Object} [config] Configuration options
+ * @cfg {boolean} [value=false] The toggle’s initial on/off state.
+ * By default, the toggle is in the 'off' state.
+ */
+OO.ui.ToggleWidget = function OoUiToggleWidget( config ) {
+ // Configuration initialization
+ config = config || {};
+
+ // Parent constructor
+ OO.ui.ToggleWidget.super.call( this, config );
+
+ // Properties
+ this.value = null;
+
+ // Initialization
+ this.$element.addClass( 'oo-ui-toggleWidget' );
+ this.setValue( !!config.value );
+};
+
+/* Setup */
+
+OO.inheritClass( OO.ui.ToggleWidget, OO.ui.Widget );
+
+/* Events */
+
+/**
+ * @event change
+ *
+ * A change event is emitted when the on/off state of the toggle changes.
+ *
+ * @param {boolean} value Value representing the new state of the toggle
+ */
+
+/* Methods */
+
+/**
+ * Get the value representing the toggle’s state.
+ *
+ * @return {boolean} The on/off state of the toggle
+ */
+OO.ui.ToggleWidget.prototype.getValue = function () {
+ return this.value;
+};
+
+/**
+ * Set the state of the toggle: `true` for 'on', `false' for 'off'.
+ *
+ * @param {boolean} value The state of the toggle
+ * @fires change
+ * @chainable
+ */
+OO.ui.ToggleWidget.prototype.setValue = function ( value ) {
+ value = !!value;
+ if ( this.value !== value ) {
+ this.value = value;
+ this.emit( 'change', value );
+ this.$element.toggleClass( 'oo-ui-toggleWidget-on', value );
+ this.$element.toggleClass( 'oo-ui-toggleWidget-off', !value );
+ this.$element.attr( 'aria-checked', value.toString() );
+ }
+ return this;
+};
diff --git a/vendor/oojs/oojs-ui/tests/Element.test.js b/vendor/oojs/oojs-ui/tests/Element.test.js
new file mode 100644
index 00000000..b37d8e35
--- /dev/null
+++ b/vendor/oojs/oojs-ui/tests/Element.test.js
@@ -0,0 +1,52 @@
+QUnit.module( 'Element', {
+ setup: function () {
+ this.fixture = document.createElement( 'div' );
+ document.body.appendChild( this.fixture );
+
+ this.makeFrame = function () {
+ var frame = document.createElement( 'iframe' );
+ this.fixture.appendChild( frame );
+ return ( frame.contentWindow && frame.contentWindow.document ) || frame.contentDocument;
+ };
+ },
+ teardown: function () {
+ this.fixture.parentNode.removeChild( this.fixture );
+ this.fixture = null;
+ }
+} );
+
+QUnit.test( 'static.getDocument', 10, function ( assert ) {
+ var frameDoc, frameEl, frameDiv,
+ el = this.fixture,
+ div = document.createElement( 'div' ),
+ $el = $( this.fixture ),
+ $div = $( '<div>' ),
+ win = window,
+ doc = document;
+
+ frameDoc = this.makeFrame();
+ frameEl = frameDoc.createElement( 'span' );
+ frameDoc.documentElement.appendChild( frameEl );
+ frameDiv = frameDoc.createElement( 'div' );
+
+ assert.strictEqual( OO.ui.Element.static.getDocument( $el ), doc, 'jQuery' );
+ assert.strictEqual( OO.ui.Element.static.getDocument( $div ), doc, 'jQuery (detached)' );
+ assert.strictEqual( OO.ui.Element.static.getDocument( el ), doc, 'HTMLElement' );
+ assert.strictEqual( OO.ui.Element.static.getDocument( div ), doc, 'HTMLElement (detached)' );
+ assert.strictEqual( OO.ui.Element.static.getDocument( win ), doc, 'Window' );
+ assert.strictEqual( OO.ui.Element.static.getDocument( doc ), doc, 'HTMLDocument' );
+
+ assert.strictEqual( OO.ui.Element.static.getDocument( frameEl ), frameDoc, 'HTMLElement (framed)' );
+ assert.strictEqual( OO.ui.Element.static.getDocument( frameDiv ), frameDoc, 'HTMLElement (framed, detached)' );
+ assert.strictEqual( OO.ui.Element.static.getDocument( frameDoc ), frameDoc, 'HTMLDocument (framed)' );
+
+ assert.strictEqual( OO.ui.Element.static.getDocument( {} ), null, 'Invalid' );
+} );
+
+QUnit.test( 'getElementDocument', 1, function ( assert ) {
+ var el, doc;
+
+ doc = document;
+ el = new OO.ui.Element();
+ assert.strictEqual( el.getElementDocument(), doc );
+} );
diff --git a/vendor/oojs/oojs-ui/tests/JSPHP.test.karma.js b/vendor/oojs/oojs-ui/tests/JSPHP.test.karma.js
new file mode 100644
index 00000000..1a1473c1
--- /dev/null
+++ b/vendor/oojs/oojs-ui/tests/JSPHP.test.karma.js
@@ -0,0 +1,61 @@
+QUnit.module( 'JSPHP' );
+
+( function () {
+ // Generate some tests based on the test suite data and HTML from PHP version.
+ var theme, klassName,
+ themes = {
+ ApexTheme: new OO.ui.ApexTheme(),
+ MediaWikiTheme: new OO.ui.MediaWikiTheme()
+ };
+
+ function unstub( value ) {
+ var config;
+ if ( typeof value === 'string' && value.substr( 0, 13 ) === '_placeholder_' ) {
+ value = JSON.parse( value.substr( 13 ) );
+ config = OO.copy( value.config, null, unstub );
+ return new OO.ui[ value.class ]( config );
+ }
+ }
+
+ function makeTest( theme, klassName, tests, output ) {
+ QUnit.test( theme + ': ' + klassName, tests.length * 2, function ( assert ) {
+ var test, config, instance, infused, $fromPhp, i, testName;
+ OO.ui.theme = themes[ theme ];
+ for ( i = 0; i < tests.length; i++ ) {
+ test = tests[ i ];
+ // Unstub placeholders
+ config = OO.copy( test.config, null, unstub );
+
+ instance = new OO.ui[ test.class ]( config );
+ $fromPhp = $( $.parseHTML( output[ i ] ) );
+
+ $( 'body' ).append( instance.$element, $fromPhp );
+
+ // Updating theme classes is normally debounced, we need to do it immediately
+ instance.debouncedUpdateThemeClasses();
+
+ testName = JSON.stringify( test.config );
+ assert.equalDomElement( instance.$element[ 0 ], $fromPhp[ 0 ], testName, true );
+
+ infused = OO.ui.infuse( $fromPhp[ 0 ] );
+ infused.debouncedUpdateThemeClasses();
+
+ assert.equalDomElement( instance.$element[ 0 ], infused.$element[ 0 ], testName + ' (infuse)' );
+ instance.$element.add( infused.$element ).detach();
+ }
+ } );
+ }
+
+ /*global testSuiteConfigs, testSuitePHPOutput */
+ for ( klassName in testSuiteConfigs ) {
+ for ( theme in themes ) {
+ makeTest(
+ theme,
+ klassName,
+ testSuiteConfigs[ klassName ],
+ testSuitePHPOutput[ theme ][ klassName ]
+ );
+ }
+ }
+
+} )();
diff --git a/vendor/oojs/oojs-ui/tests/JSPHP.test.standalone.js b/vendor/oojs/oojs-ui/tests/JSPHP.test.standalone.js
new file mode 100644
index 00000000..1cbebc3a
--- /dev/null
+++ b/vendor/oojs/oojs-ui/tests/JSPHP.test.standalone.js
@@ -0,0 +1,55 @@
+QUnit.module( 'JSPHP' );
+
+( function () {
+ // Generate some tests based on the test suite data and HTML from PHP version.
+ var theme, klassName,
+ themes = {
+ ApexTheme: new OO.ui.ApexTheme(),
+ MediaWikiTheme: new OO.ui.MediaWikiTheme()
+ };
+
+ function unstub( value ) {
+ var config;
+ if ( typeof value === 'string' && value.substr( 0, 13 ) === '_placeholder_' ) {
+ value = JSON.parse( value.substr( 13 ) );
+ config = OO.copy( value.config, null, unstub );
+ return new OO.ui[ value.class ]( config );
+ }
+ }
+
+ function makeTest( theme, klassName, tests ) {
+ QUnit.test( theme + ': ' + klassName, tests.length * 2, function ( assert ) {
+ var test, config, instance, infused, id, fromPhp, i, testName;
+ OO.ui.theme = themes[ theme ];
+ for ( i = 0; i < tests.length; i++ ) {
+ test = tests[ i ];
+ // Unstub placeholders
+ config = OO.copy( test.config, null, unstub );
+
+ instance = new OO.ui[ test.class ]( config );
+
+ id = 'JSPHPTestSuite_' + theme + klassName + i;
+ fromPhp = document.getElementById( id ).firstChild;
+ instance.$element.insertBefore( fromPhp );
+
+ // Updating theme classes is normally debounced, we need to do it immediately
+ instance.debouncedUpdateThemeClasses();
+
+ testName = JSON.stringify( test.config );
+ assert.equalDomElement( instance.$element[ 0 ], fromPhp, testName );
+
+ infused = OO.ui.infuse( fromPhp );
+ infused.debouncedUpdateThemeClasses();
+
+ assert.equalDomElement( instance.$element[ 0 ], infused.$element[ 0 ], testName + ' (infuse)' );
+ }
+ } );
+ }
+
+ for ( klassName in OO.ui.JSPHPTestSuite ) {
+ for ( theme in themes ) {
+ makeTest( theme, klassName, OO.ui.JSPHPTestSuite[ klassName ] );
+ }
+ }
+
+} )();
diff --git a/vendor/oojs/oojs-ui/tests/Process.test.js b/vendor/oojs/oojs-ui/tests/Process.test.js
new file mode 100644
index 00000000..3f036407
--- /dev/null
+++ b/vendor/oojs/oojs-ui/tests/Process.test.js
@@ -0,0 +1,179 @@
+QUnit.module( 'OO.ui.Process' );
+
+/* Tests */
+
+QUnit.test( 'next', 1, function ( assert ) {
+ var process = new OO.ui.Process(),
+ result = [];
+
+ process
+ .next( function () {
+ result.push( 0 );
+ } )
+ .next( function () {
+ result.push( 1 );
+ } )
+ .next( function () {
+ result.push( 2 );
+ } )
+ .execute();
+
+ assert.deepEqual( result, [ 0, 1, 2 ], 'Steps can be added at the end' );
+} );
+
+QUnit.test( 'first', 1, function ( assert ) {
+ var process = new OO.ui.Process(),
+ result = [];
+
+ process
+ .first( function () {
+ result.push( 0 );
+ } )
+ .first( function () {
+ result.push( 1 );
+ } )
+ .first( function () {
+ result.push( 2 );
+ } )
+ .execute();
+
+ assert.deepEqual( result, [ 2, 1, 0 ], 'Steps can be added at the beginning' );
+} );
+
+QUnit.asyncTest( 'execute (async)', 1, function ( assert ) {
+ // Async
+ var process = new OO.ui.Process(),
+ result = [];
+
+ process
+ .next( function () {
+ var deferred = $.Deferred();
+
+ setTimeout( function () {
+ result.push( 1 );
+ deferred.resolve();
+ }, 10 );
+
+ return deferred.promise();
+ } )
+ .first( function () {
+ var deferred = $.Deferred();
+
+ setTimeout( function () {
+ result.push( 0 );
+ deferred.resolve();
+ }, 10 );
+
+ return deferred.promise();
+ } )
+ .next( function () {
+ result.push( 2 );
+ } );
+
+ process.execute().done( function () {
+ assert.deepEqual(
+ result,
+ [ 0, 1, 2 ],
+ 'Synchronous and asynchronous steps are executed in the correct order'
+ );
+ QUnit.start();
+ } );
+} );
+
+QUnit.asyncTest( 'execute (return false)', 1, function ( assert ) {
+ var process = new OO.ui.Process(),
+ result = [];
+
+ process
+ .next( function () {
+ var deferred = $.Deferred();
+
+ setTimeout( function () {
+ result.push( 0 );
+ deferred.resolve();
+ }, 10 );
+
+ return deferred.promise();
+ } )
+ .next( function () {
+ result.push( 1 );
+ return false;
+ } )
+ .next( function () {
+ // Should never be run because previous step is rejected
+ result.push( 2 );
+ } );
+
+ process.execute().fail( function () {
+ assert.deepEqual(
+ result,
+ [ 0, 1 ],
+ 'Process is stopped when a step returns false'
+ );
+ QUnit.start();
+ } );
+} );
+
+QUnit.asyncTest( 'execute (async reject)', 1, function ( assert ) {
+ var process = new OO.ui.Process(),
+ result = [];
+
+ process
+ .next( function () {
+ result.push( 0 );
+ } )
+ .next( function () {
+ var deferred = $.Deferred();
+
+ setTimeout( function () {
+ result.push( 1 );
+ deferred.reject();
+ }, 10 );
+
+ return deferred.promise();
+ } )
+ .next( function () {
+ // Should never be run because previous step is rejected
+ result.push( 2 );
+ } );
+
+ process.execute().fail( function () {
+ assert.deepEqual(
+ result,
+ [ 0, 1 ],
+ 'Process is stopped when a step returns a promise that is then rejected'
+ );
+ QUnit.start();
+ } );
+} );
+
+QUnit.asyncTest( 'execute (wait)', 1, function ( assert ) {
+ var process = new OO.ui.Process(),
+ result = [];
+
+ process
+ .next( function () {
+ result.push( 'A' );
+ return 10;
+ } )
+ .next( function () {
+ result.push( 'B' );
+ } );
+
+ // Steps defined above don't run until execute()
+ result.push( 'before' );
+
+ // Process yields between step A and B
+ setTimeout( function () {
+ result.push( 'yield' );
+ } );
+
+ process.execute().done( function () {
+ assert.deepEqual(
+ result,
+ [ 'before', 'A', 'yield', 'B' ],
+ 'Process is stopped when a step returns a promise that is then rejected'
+ );
+ QUnit.start();
+ } );
+} );
diff --git a/vendor/oojs/oojs-ui/tests/QUnit.assert.equalDomElement.js b/vendor/oojs/oojs-ui/tests/QUnit.assert.equalDomElement.js
new file mode 100644
index 00000000..f041c258
--- /dev/null
+++ b/vendor/oojs/oojs-ui/tests/QUnit.assert.equalDomElement.js
@@ -0,0 +1,113 @@
+/*!
+ * A QUnit assertion to compare DOM node trees.
+ *
+ * Adapted from VisualEditor plugin for QUnit. Additionally supports comparing properties to
+ * attributes (for dynamically generated nodes) and order-insensitive comparison of classes on DOM
+ * nodes.
+ *
+ * @copyright 2011-2015 VisualEditor Team and others; see http://ve.mit-license.org
+ * @copyright 2011-2015 OOjs Team and other contributors
+ */
+
+( function ( QUnit ) {
+
+ /**
+ * Build a summary of an HTML element.
+ *
+ * Summaries include node name, text, attributes and recursive summaries of children.
+ * Used for serializing or comparing HTML elements.
+ *
+ * @private
+ * @param {HTMLElement} element Element to summarize
+ * @param {boolean} [includeHtml=false] Include an HTML summary for element nodes
+ * @return {Object} Summary of element.
+ */
+ function getDomElementSummary( element, includeHtml ) {
+ var i, name,
+ summary = {
+ type: element.nodeName.toLowerCase(),
+ // $( '<div><textarea>Foo</textarea></div>' )[0].textContent === 'Foo', which breaks
+ // comparisons :( childNodes are summarized anyway, this would just be a nicety
+ // text: element.textContent,
+ attributes: {},
+ children: []
+ };
+
+ if ( includeHtml && element.nodeType === Node.ELEMENT_NODE ) {
+ summary.html = element.outerHTML;
+ }
+
+ // Gather attributes
+ if ( element.attributes ) {
+ for ( i = 0; i < element.attributes.length; i++ ) {
+ name = element.attributes[ i ].name;
+ if ( name.substr( 0, 5 ) !== 'data-' && name !== 'id' ) {
+ summary.attributes[ name ] = element.attributes[ i ].value;
+ }
+ }
+ }
+
+ // Sort classes
+ if ( summary.attributes.class ) {
+ summary.attributes.class = summary.attributes.class.split( ' ' ).sort().join( ' ' );
+ }
+
+ // Gather certain properties and pretend they are attributes.
+ // Take note of casing differences.
+ if ( element.value !== undefined ) {
+ summary.attributes.value = element.value;
+ }
+ if ( element.readOnly !== undefined ) {
+ summary.attributes.readonly = element.readOnly;
+ }
+ if ( element.checked !== undefined ) {
+ summary.attributes.checked = element.checked;
+ }
+ if ( element.disabled !== undefined ) {
+ summary.attributes.disabled = element.disabled;
+ }
+ if ( element.tabIndex !== undefined ) {
+ summary.attributes.tabindex = element.tabIndex;
+ }
+
+ // Summarize children
+ if ( element.childNodes ) {
+ for ( i = 0; i < element.childNodes.length; i++ ) {
+ summary.children.push( getDomElementSummary( element.childNodes[ i ], includeHtml ) );
+ }
+ }
+
+ // Special handling for textareas, where we only want to account for the content as the 'value'
+ // property, never as childNodes or textContent
+ if ( summary.type === 'textarea' ) {
+ // summary.text = '';
+ summary.children = [];
+ }
+
+ return summary;
+ }
+
+ /**
+ * @method
+ * @static
+ */
+ QUnit.assert.equalDomElement = function ( actual, expected, message, stringify ) {
+ var actualSummary = getDomElementSummary( actual ),
+ expectedSummary = getDomElementSummary( expected ),
+ actualSummaryHtml = getDomElementSummary( actual, true ),
+ expectedSummaryHtml = getDomElementSummary( expected, true );
+
+ // When running with Karma, the objects are not nicely stringified in the output when the test
+ // fails, only showing "Expected: [object Object], Actual: [object Object]" instead. Running
+ // QUnit in browser does this, and stringifying causes double escaping in output.
+ if ( stringify ) {
+ actualSummaryHtml = JSON.stringify( actualSummaryHtml, null, 2 );
+ expectedSummaryHtml = JSON.stringify( expectedSummaryHtml, null, 2 );
+ }
+
+ QUnit.push(
+ QUnit.equiv( actualSummary, expectedSummary ), actualSummaryHtml, expectedSummaryHtml, message
+ );
+ };
+
+}( QUnit ) );
diff --git a/vendor/oojs/oojs-ui/tests/elements/FlaggedElement.test.js b/vendor/oojs/oojs-ui/tests/elements/FlaggedElement.test.js
new file mode 100644
index 00000000..f5c483ad
--- /dev/null
+++ b/vendor/oojs/oojs-ui/tests/elements/FlaggedElement.test.js
@@ -0,0 +1,64 @@
+( function () {
+ QUnit.module( 'FlaggedElement' );
+
+ function TestElement( config ) {
+ TestElement.super.call( this, config );
+ OO.ui.FlaggedElement.call( this, config );
+ }
+ OO.inheritClass( TestElement, OO.ui.Widget );
+ OO.mixinClass( TestElement, OO.ui.FlaggedElement );
+
+ QUnit.test( 'constructor', 2, function ( assert ) {
+ var element;
+
+ element = new TestElement();
+ assert.deepEqual( element.getFlags(), [], 'No flags by default' );
+
+ element = new TestElement( {
+ flags: [ 'foo' ]
+ } );
+ assert.deepEqual( element.getFlags(), [ 'foo' ], 'Config option "flags"' );
+ } );
+
+ QUnit.test( 'getFlags', 2, function ( assert ) {
+ var element = new TestElement();
+
+ element.setFlags( 'foo' );
+ assert.deepEqual( element.getFlags(), [ 'foo' ], 'Flag was set' );
+
+ element.clearFlags();
+ assert.deepEqual( element.getFlags(), [], 'Flag was removed' );
+ } );
+
+ QUnit.test( 'hasFlag', 3, function ( assert ) {
+ var element = new TestElement();
+ assert.deepEqual( element.hasFlag( 'foo' ), false, 'Flag absent by default' );
+
+ element.setFlags( 'foo' );
+ assert.deepEqual( element.hasFlag( 'foo' ), true, 'Flag was set' );
+
+ element.clearFlags();
+ assert.deepEqual( element.hasFlag( 'foo' ), false, 'Flag was removed' );
+ } );
+
+ QUnit.test( 'clearFlags', 1, function ( assert ) {
+ var element = new TestElement();
+ element.setFlags( 'foo' );
+ element.clearFlags();
+ assert.deepEqual( element.hasFlag( 'foo' ), false, 'Flag was removed' );
+ } );
+
+ QUnit.test( 'setFlags', 5, function ( assert ) {
+ var element = new TestElement();
+ element.setFlags( 'foo' );
+ assert.deepEqual( element.hasFlag( 'foo' ), true, 'string' );
+
+ element.setFlags( [ 'bar', 'qux' ] );
+ assert.deepEqual( element.hasFlag( 'bar' ), true, 'array[ 0 ]' );
+ assert.deepEqual( element.hasFlag( 'qux' ), true, 'array[ 1 ]' );
+
+ element.setFlags( { bar: false, quux: true } );
+ assert.deepEqual( element.hasFlag( 'bar' ), false, 'object set' );
+ assert.deepEqual( element.hasFlag( 'quux' ), true, 'object remove' );
+ } );
+}() );
diff --git a/vendor/oojs/oojs-ui/tests/index.php b/vendor/oojs/oojs-ui/tests/index.php
new file mode 100644
index 00000000..d8e06835
--- /dev/null
+++ b/vendor/oojs/oojs-ui/tests/index.php
@@ -0,0 +1,77 @@
+<?php
+ $autoload = '../vendor/autoload.php';
+ if ( !file_exists( $autoload ) ) {
+ echo '<h1>Did you forget to run <code>composer install</code>?</h1>';
+ exit;
+ }
+ require_once $autoload;
+
+ $testSuiteFile = 'JSPHP-suite.json';
+ if ( !file_exists( $testSuiteFile ) ) {
+ echo '<h1>Did you forget to run <code>grunt build</code>?</h1>';
+ exit;
+ }
+ $testSuiteJSON = file_get_contents( $testSuiteFile );
+ $testSuite = json_decode( $testSuiteJSON, true );
+?>
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+ <meta charset="UTF-8">
+ <title>OOjs UI Test Suite</title>
+ <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
+ <script src="../node_modules/qunitjs/qunit/qunit.js"></script>
+ <script src="./QUnit.assert.equalDomElement.js"></script>
+ <script>
+ QUnit.config.requireExpects = true;
+ </script>
+ <!-- Dependencies -->
+ <script src="../node_modules/jquery/dist/jquery.js"></script>
+ <script src="../node_modules/oojs/dist/oojs.jquery.js"></script>
+ <!-- Source code -->
+ <script src="../dist/oojs-ui.js"></script>
+ <script src="../dist/oojs-ui-apex.js"></script>
+ <script src="../dist/oojs-ui-mediawiki.js"></script>
+ <!-- Test suites -->
+ <script src="./Element.test.js"></script>
+ <script src="./Process.test.js"></script>
+ <script src="./elements/FlaggedElement.test.js"></script>
+ <!-- JS/PHP comparison tests -->
+ <script>OO.ui.JSPHPTestSuite = <?php echo $testSuiteJSON; ?></script>
+ <script src="./JSPHP.test.standalone.js"></script>
+</head>
+<body>
+ <div id="JSPHPTestSuite" style="display: none;">
+ <?php
+ function new_OOUI( $class, $config = array() ) {
+ $class = "OOUI\\" . $class;
+ return new $class( $config );
+ }
+ function unstub( &$value ) {
+ if ( is_string( $value ) && substr( $value, 0, 13 ) === '_placeholder_' ) {
+ $value = json_decode( substr( $value, 13 ), true );
+ array_walk_recursive( $value['config'], 'unstub' );
+ $value = new_OOUI( $value['class'], $value['config'] );
+ }
+ }
+ // Keep synchronized with bin/generate-JSPHP-for-karma.php
+ $themes = array( 'ApexTheme', 'MediaWikiTheme' );
+ foreach ( $themes as $theme ) {
+ OOUI\Theme::setSingleton( new_OOUI( $theme ) );
+ foreach ( $testSuite as $className => $tests ) {
+ foreach ( $tests as $index => $test ) {
+ // Unstub placeholders
+ $config = $test['config'];
+ array_walk_recursive( $config, 'unstub' );
+ $config['infusable'] = true;
+ $instance = new_OOUI( $test['class'], $config );
+ echo "<div id='JSPHPTestSuite_$theme$className$index'>$instance</div>\n";
+ }
+ }
+ }
+ ?>
+ </div>
+ <div id="qunit"></div>
+ <div id="qunit-fixture"></div>
+</body>
+</html>
diff --git a/vendor/psr/log/LICENSE b/vendor/psr/log/LICENSE
new file mode 100644
index 00000000..474c952b
--- /dev/null
+++ b/vendor/psr/log/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2012 PHP Framework Interoperability Group
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/psr/log/Psr/Log/AbstractLogger.php b/vendor/psr/log/Psr/Log/AbstractLogger.php
new file mode 100644
index 00000000..00f90345
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/AbstractLogger.php
@@ -0,0 +1,120 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger implementation that other Loggers can inherit from.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+abstract class AbstractLogger implements LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function emergency($message, array $context = array())
+ {
+ $this->log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
new file mode 100644
index 00000000..67f852d1
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace Psr\Log;
+
+class InvalidArgumentException extends \InvalidArgumentException
+{
+}
diff --git a/vendor/psr/log/Psr/Log/LogLevel.php b/vendor/psr/log/Psr/Log/LogLevel.php
new file mode 100644
index 00000000..e32c151c
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LogLevel.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes log levels
+ */
+class LogLevel
+{
+ const EMERGENCY = 'emergency';
+ const ALERT = 'alert';
+ const CRITICAL = 'critical';
+ const ERROR = 'error';
+ const WARNING = 'warning';
+ const NOTICE = 'notice';
+ const INFO = 'info';
+ const DEBUG = 'debug';
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerAwareInterface.php b/vendor/psr/log/Psr/Log/LoggerAwareInterface.php
new file mode 100644
index 00000000..2eebc4eb
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerAwareInterface.php
@@ -0,0 +1,17 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger-aware instance
+ */
+interface LoggerAwareInterface
+{
+ /**
+ * Sets a logger instance on the object
+ *
+ * @param LoggerInterface $logger
+ * @return null
+ */
+ public function setLogger(LoggerInterface $logger);
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerAwareTrait.php b/vendor/psr/log/Psr/Log/LoggerAwareTrait.php
new file mode 100644
index 00000000..f087a3da
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerAwareTrait.php
@@ -0,0 +1,22 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Basic Implementation of LoggerAwareInterface.
+ */
+trait LoggerAwareTrait
+{
+ /** @var LoggerInterface */
+ protected $logger;
+
+ /**
+ * Sets a logger.
+ *
+ * @param LoggerInterface $logger
+ */
+ public function setLogger(LoggerInterface $logger)
+ {
+ $this->logger = $logger;
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php
new file mode 100644
index 00000000..476bb962
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerInterface.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * Describes a logger instance
+ *
+ * The message MUST be a string or object implementing __toString().
+ *
+ * The message MAY contain placeholders in the form: {foo} where foo
+ * will be replaced by the context data in key "foo".
+ *
+ * The context array can contain arbitrary data, the only assumption that
+ * can be made by implementors is that if an Exception instance is given
+ * to produce a stack trace, it MUST be in a key named "exception".
+ *
+ * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md
+ * for the full interface specification.
+ */
+interface LoggerInterface
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function emergency($message, array $context = array());
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function alert($message, array $context = array());
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function critical($message, array $context = array());
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function error($message, array $context = array());
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function warning($message, array $context = array());
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function notice($message, array $context = array());
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function info($message, array $context = array());
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function debug($message, array $context = array());
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function log($level, $message, array $context = array());
+}
diff --git a/vendor/psr/log/Psr/Log/LoggerTrait.php b/vendor/psr/log/Psr/Log/LoggerTrait.php
new file mode 100644
index 00000000..59124960
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/LoggerTrait.php
@@ -0,0 +1,131 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This is a simple Logger trait that classes unable to extend AbstractLogger
+ * (because they extend another class, etc) can include.
+ *
+ * It simply delegates all log-level-specific methods to the `log` method to
+ * reduce boilerplate code that a simple Logger that does the same thing with
+ * messages regardless of the error level has to implement.
+ */
+trait LoggerTrait
+{
+ /**
+ * System is unusable.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function emergency($message, array $context = array())
+ {
+ $this->log(LogLevel::EMERGENCY, $message, $context);
+ }
+
+ /**
+ * Action must be taken immediately.
+ *
+ * Example: Entire website down, database unavailable, etc. This should
+ * trigger the SMS alerts and wake you up.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function alert($message, array $context = array())
+ {
+ $this->log(LogLevel::ALERT, $message, $context);
+ }
+
+ /**
+ * Critical conditions.
+ *
+ * Example: Application component unavailable, unexpected exception.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function critical($message, array $context = array())
+ {
+ $this->log(LogLevel::CRITICAL, $message, $context);
+ }
+
+ /**
+ * Runtime errors that do not require immediate action but should typically
+ * be logged and monitored.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function error($message, array $context = array())
+ {
+ $this->log(LogLevel::ERROR, $message, $context);
+ }
+
+ /**
+ * Exceptional occurrences that are not errors.
+ *
+ * Example: Use of deprecated APIs, poor use of an API, undesirable things
+ * that are not necessarily wrong.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function warning($message, array $context = array())
+ {
+ $this->log(LogLevel::WARNING, $message, $context);
+ }
+
+ /**
+ * Normal but significant events.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function notice($message, array $context = array())
+ {
+ $this->log(LogLevel::NOTICE, $message, $context);
+ }
+
+ /**
+ * Interesting events.
+ *
+ * Example: User logs in, SQL logs.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function info($message, array $context = array())
+ {
+ $this->log(LogLevel::INFO, $message, $context);
+ }
+
+ /**
+ * Detailed debug information.
+ *
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function debug($message, array $context = array())
+ {
+ $this->log(LogLevel::DEBUG, $message, $context);
+ }
+
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ abstract public function log($level, $message, array $context = array());
+}
diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php
new file mode 100644
index 00000000..553a3c59
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/NullLogger.php
@@ -0,0 +1,27 @@
+<?php
+
+namespace Psr\Log;
+
+/**
+ * This Logger can be used to avoid conditional log calls
+ *
+ * Logging should always be optional, and if no logger is provided to your
+ * library creating a NullLogger instance to have something to throw logs at
+ * is a good way to avoid littering your code with `if ($this->logger) { }`
+ * blocks.
+ */
+class NullLogger extends AbstractLogger
+{
+ /**
+ * Logs with an arbitrary level.
+ *
+ * @param mixed $level
+ * @param string $message
+ * @param array $context
+ * @return null
+ */
+ public function log($level, $message, array $context = array())
+ {
+ // noop
+ }
+}
diff --git a/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
new file mode 100644
index 00000000..a9328151
--- /dev/null
+++ b/vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php
@@ -0,0 +1,116 @@
+<?php
+
+namespace Psr\Log\Test;
+
+use Psr\Log\LogLevel;
+
+/**
+ * Provides a base test class for ensuring compliance with the LoggerInterface
+ *
+ * Implementors can extend the class and implement abstract methods to run this as part of their test suite
+ */
+abstract class LoggerInterfaceTest extends \PHPUnit_Framework_TestCase
+{
+ /**
+ * @return LoggerInterface
+ */
+ abstract function getLogger();
+
+ /**
+ * This must return the log messages in order with a simple formatting: "<LOG LEVEL> <MESSAGE>"
+ *
+ * Example ->error('Foo') would yield "error Foo"
+ *
+ * @return string[]
+ */
+ abstract function getLogs();
+
+ public function testImplements()
+ {
+ $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger());
+ }
+
+ /**
+ * @dataProvider provideLevelsAndMessages
+ */
+ public function testLogsAtAllLevels($level, $message)
+ {
+ $logger = $this->getLogger();
+ $logger->{$level}($message, array('user' => 'Bob'));
+ $logger->log($level, $message, array('user' => 'Bob'));
+
+ $expected = array(
+ $level.' message of level '.$level.' with context: Bob',
+ $level.' message of level '.$level.' with context: Bob',
+ );
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function provideLevelsAndMessages()
+ {
+ return array(
+ LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'),
+ LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'),
+ LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'),
+ LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'),
+ LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'),
+ LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'),
+ LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'),
+ LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'),
+ );
+ }
+
+ /**
+ * @expectedException Psr\Log\InvalidArgumentException
+ */
+ public function testThrowsOnInvalidLevel()
+ {
+ $logger = $this->getLogger();
+ $logger->log('invalid level', 'Foo');
+ }
+
+ public function testContextReplacement()
+ {
+ $logger = $this->getLogger();
+ $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar'));
+
+ $expected = array('info {Message {nothing} Bob Bar a}');
+ $this->assertEquals($expected, $this->getLogs());
+ }
+
+ public function testObjectCastToString()
+ {
+ $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString'));
+ $dummy->expects($this->once())
+ ->method('__toString')
+ ->will($this->returnValue('DUMMY'));
+
+ $this->getLogger()->warning($dummy);
+ }
+
+ public function testContextCanContainAnything()
+ {
+ $context = array(
+ 'bool' => true,
+ 'null' => null,
+ 'string' => 'Foo',
+ 'int' => 0,
+ 'float' => 0.5,
+ 'nested' => array('with object' => new DummyTest),
+ 'object' => new \DateTime,
+ 'resource' => fopen('php://memory', 'r'),
+ );
+
+ $this->getLogger()->warning('Crazy context data', $context);
+ }
+
+ public function testContextExceptionKeyCanBeExceptionOrOtherValues()
+ {
+ $this->getLogger()->warning('Random message', array('exception' => 'oops'));
+ $this->getLogger()->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail')));
+ }
+}
+
+class DummyTest
+{
+} \ No newline at end of file
diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md
new file mode 100644
index 00000000..574bc1cb
--- /dev/null
+++ b/vendor/psr/log/README.md
@@ -0,0 +1,45 @@
+PSR Log
+=======
+
+This repository holds all interfaces/classes/traits related to
+[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md).
+
+Note that this is not a logger of its own. It is merely an interface that
+describes a logger. See the specification for more details.
+
+Usage
+-----
+
+If you need a logger, you can use the interface like this:
+
+```php
+<?php
+
+use Psr\Log\LoggerInterface;
+
+class Foo
+{
+ private $logger;
+
+ public function __construct(LoggerInterface $logger = null)
+ {
+ $this->logger = $logger;
+ }
+
+ public function doSomething()
+ {
+ if ($this->logger) {
+ $this->logger->info('Doing work');
+ }
+
+ // do something useful
+ }
+}
+```
+
+You can then pick one of the implementations of the interface to get a logger.
+
+If you want to implement the interface, you can require this package and
+implement `Psr\Log\LoggerInterface` in your code. Please read the
+[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md)
+for details.
diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json
new file mode 100644
index 00000000..6bdcc219
--- /dev/null
+++ b/vendor/psr/log/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "psr/log",
+ "description": "Common interface for logging libraries",
+ "keywords": ["psr", "psr-3", "log"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "http://www.php-fig.org/"
+ }
+ ],
+ "autoload": {
+ "psr-0": {
+ "Psr\\Log\\": ""
+ }
+ }
+}
diff --git a/vendor/wikimedia/cdb/COPYING b/vendor/wikimedia/cdb/COPYING
new file mode 100644
index 00000000..019694a9
--- /dev/null
+++ b/vendor/wikimedia/cdb/COPYING
@@ -0,0 +1,342 @@
+== GNU GENERAL PUBLIC LICENSE ==
+
+Version 2, June 1991
+
+Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+
+=== Preamble ===
+
+The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+The precise terms and conditions for copying, distribution and
+modification follow.
+
+== TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION ==
+
+'''0.''' This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+'''1.''' You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+'''2.''' You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ '''a)''' You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ '''b)''' You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ '''c)''' If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+'''3.''' You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ '''a)''' Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ '''b)''' Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ '''c)''' Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+'''4.''' You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+'''5.''' You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+'''6.''' Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+'''7.''' If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+'''8.''' If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+'''9.''' The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+'''10.''' If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+=== NO WARRANTY ===
+
+'''11.''' BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+'''12.''' IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ '''END OF TERMS AND CONDITIONS'''
+
+== How to Apply These Terms to Your New Programs ==
+
+If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/vendor/wikimedia/cdb/Doxyfile b/vendor/wikimedia/cdb/Doxyfile
new file mode 100644
index 00000000..e77e75ae
--- /dev/null
+++ b/vendor/wikimedia/cdb/Doxyfile
@@ -0,0 +1,32 @@
+# Configuration file for Doxygen
+
+PROJECT_NAME = CDB
+PROJECT_BRIEF = CDB functions for PHP
+
+OUTPUT_DIRECTORY = doc
+
+JAVADOC_AUTOBRIEF = YES
+QT_AUTOBRIEF = YES
+
+WARN_NO_PARAMDOC = YES
+
+INPUT = README.md src/
+FILE_PATTERNS = *.php
+RECURSIVE = YES
+# Requires doxygen 1.8.3+
+USE_MDFILE_AS_MAINPAGE = README.md
+
+HTML_DYNAMIC_SECTIONS = YES
+GENERATE_TREEVIEW = YES
+TREEVIEW_WIDTH = 250
+
+GENERATE_LATEX = NO
+
+HAVE_DOT = YES
+DOT_FONTNAME = Helvetica
+DOT_FONTSIZE = 10
+TEMPLATE_RELATIONS = YES
+CALL_GRAPH = NO
+CALLER_GRAPH = NO
+# Makes dot run faster. Requires graphviz >1.8.10
+DOT_MULTI_TARGETS = YES
diff --git a/vendor/wikimedia/cdb/README.md b/vendor/wikimedia/cdb/README.md
new file mode 100644
index 00000000..e4ef4498
--- /dev/null
+++ b/vendor/wikimedia/cdb/README.md
@@ -0,0 +1,48 @@
+[![Latest Stable Version](https://poser.pugx.org/cdb/cdb/v/stable.svg)](https://packagist.org/packages/cdb/cdb) [![License](https://poser.pugx.org/cdb/cdb/license.svg)](https://packagist.org/packages/cdb/cdb)
+
+CDB functions for PHP
+=====================
+
+[CDB][], short for "constant database", refers to a very fast and highly
+reliable database system which uses a simple file with key value pairs. This
+library wraps the CDB functionality exposed in PHP via the `dba_*` functions.
+In cases where `dba_*` functions are not present or are not compiled with CDB
+support, a pure-PHP implementation is provided for falling back.
+
+Additional documentation about the library can be found on [MediaWiki.org](https://www.mediawiki.org/wiki/CDB).
+
+
+Usage
+-----
+
+```
+// Reading a CDB file
+$cdb = \Cdb\Reader::open( 'db.cdb' );
+$foo = $cdb->get( 'somekey' );
+
+// Writing to a CDB file
+$cdb = \Cdb\Writer::open( 'anotherdb.cdb' );
+$cdb->set( 'somekey', $foo );
+```
+
+Running tests
+-------------
+
+```
+composer install --prefer-dist
+cd test
+../vendor/phpunit/phpunit/phpunit .
+```
+
+History
+-------
+
+This library was first introduced in [MediaWiki 1.16][] ([r52203][]). It was
+split out of the MediaWiki codebase and published as an independent library
+during the [MediaWiki 1.25][] development cycle.
+
+---
+[CDB]: https://en.wikipedia.org/wiki/cdb_(software)
+[MediaWiki 1.16]: https://www.mediawiki.org/wiki/MediaWiki_1.16
+[r52203]: https://www.mediawiki.org/wiki/Special:Code/MediaWiki/52203
+[MediaWiki 1.25]: https://www.mediawiki.org/wiki/MediaWiki_1.25
diff --git a/vendor/wikimedia/cdb/composer.json b/vendor/wikimedia/cdb/composer.json
new file mode 100644
index 00000000..2134d2f7
--- /dev/null
+++ b/vendor/wikimedia/cdb/composer.json
@@ -0,0 +1,26 @@
+{
+ "name": "wikimedia/cdb",
+ "description": "Constant Database (CDB) wrapper library for PHP. Provides pure-PHP fallback when dba_* functions are absent.",
+ "license": "GPL-2.0",
+ "homepage": "https://www.mediawiki.org/wiki/CDB",
+ "authors": [
+ {
+ "name": "Tim Starling",
+ "email": "tstarling@wikimedia.org"
+ },
+ {
+ "name": "Chad Horohoe",
+ "email": "chad@wikimedia.org"
+ }
+ ],
+ "minimum-stability": "dev",
+ "require": {
+ "php": ">=5.3.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "*"
+ },
+ "autoload": {
+ "classmap": ["src/"]
+ }
+}
diff --git a/vendor/wikimedia/cdb/doc/README b/vendor/wikimedia/cdb/doc/README
new file mode 100644
index 00000000..15ee3b71
--- /dev/null
+++ b/vendor/wikimedia/cdb/doc/README
@@ -0,0 +1 @@
+Placeholder for doxygen documentation
diff --git a/vendor/wikimedia/cdb/src/Exception.php b/vendor/wikimedia/cdb/src/Exception.php
new file mode 100644
index 00000000..76a59da2
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Exception.php
@@ -0,0 +1,28 @@
+<?php
+
+namespace Cdb;
+
+/**
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Exception for Cdb errors.
+ */
+class Exception extends \Exception {
+}
diff --git a/vendor/wikimedia/cdb/src/Reader.php b/vendor/wikimedia/cdb/src/Reader.php
new file mode 100644
index 00000000..2df06ab3
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Reader.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace Cdb;
+
+/**
+ * Native CDB file reader and writer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Read from a CDB file.
+ * Native and pure PHP implementations are provided.
+ * http://cr.yp.to/cdb.html
+ */
+abstract class Reader {
+ /**
+ * The file handle
+ */
+ protected $handle;
+
+ /**
+ * Open a file and return a subclass instance
+ *
+ * @param string $fileName
+ *
+ * @return Reader
+ */
+ public static function open( $fileName ) {
+ return self::haveExtension() ?
+ new Reader\DBA( $fileName ) :
+ new Reader\PHP( $fileName );
+ }
+
+ /**
+ * Returns true if the native extension is available
+ *
+ * @return bool
+ */
+ public static function haveExtension() {
+ if ( !function_exists( 'dba_handlers' ) ) {
+ return false;
+ }
+ $handlers = dba_handlers();
+ if ( !in_array( 'cdb', $handlers ) || !in_array( 'cdb_make', $handlers ) ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Create the object and open the file
+ *
+ * @param string $fileName
+ */
+ abstract public function __construct( $fileName );
+
+ /**
+ * Close the file. Optional, you can just let the variable go out of scope.
+ */
+ abstract public function close();
+
+ /**
+ * Get a value with a given key. Only string values are supported.
+ *
+ * @param string $key
+ */
+ abstract public function get( $key );
+}
diff --git a/vendor/wikimedia/cdb/src/Reader/DBA.php b/vendor/wikimedia/cdb/src/Reader/DBA.php
new file mode 100644
index 00000000..838bf6e0
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Reader/DBA.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Cdb\Reader;
+use Cdb\Exception;
+use Cdb\Reader;
+
+/**
+ * DBA-based CDB reader/writer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Reader class which uses the DBA extension
+ */
+class DBA extends Reader {
+ public function __construct( $fileName ) {
+ $this->handle = dba_open( $fileName, 'r-', 'cdb' );
+ if ( !$this->handle ) {
+ throw new Exception( 'Unable to open CDB file "' . $fileName . '"' );
+ }
+ }
+
+ public function close() {
+ if ( isset( $this->handle ) ) {
+ dba_close( $this->handle );
+ }
+ unset( $this->handle );
+ }
+
+ public function get( $key ) {
+ return dba_fetch( $key, $this->handle );
+ }
+}
diff --git a/vendor/wikimedia/cdb/src/Reader/PHP.php b/vendor/wikimedia/cdb/src/Reader/PHP.php
new file mode 100644
index 00000000..57b7d8ca
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Reader/PHP.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace Cdb\Reader;
+use Cdb\Exception;
+use Cdb\Reader;
+use Cdb\Util;
+
+/**
+ * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
+ * appears in PHP 5.3. Changes are:
+ * * Error returns replaced with exceptions
+ * * Exception thrown if sizes or offsets are between 2GB and 4GB
+ * * Some variables renamed
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * CDB reader class
+ */
+class PHP extends Reader {
+ /** The filename */
+ protected $fileName;
+
+ /* number of hash slots searched under this key */
+ protected $loop;
+
+ /* initialized if loop is nonzero */
+ protected $khash;
+
+ /* initialized if loop is nonzero */
+ protected $kpos;
+
+ /* initialized if loop is nonzero */
+ protected $hpos;
+
+ /* initialized if loop is nonzero */
+ protected $hslots;
+
+ /* initialized if findNext() returns true */
+ protected $dpos;
+
+ /* initialized if cdb_findnext() returns 1 */
+ protected $dlen;
+
+ /**
+ * @param string $fileName
+ * @throws Exception
+ */
+ public function __construct( $fileName ) {
+ $this->fileName = $fileName;
+ $this->handle = fopen( $fileName, 'rb' );
+ if ( !$this->handle ) {
+ throw new Exception( 'Unable to open CDB file "' . $this->fileName . '".' );
+ }
+ $this->findStart();
+ }
+
+ public function close() {
+ if ( isset( $this->handle ) ) {
+ fclose( $this->handle );
+ }
+ unset( $this->handle );
+ }
+
+ /**
+ * @param mixed $key
+ * @return bool|string
+ */
+ public function get( $key ) {
+ // strval is required
+ if ( $this->find( strval( $key ) ) ) {
+ return $this->read( $this->dlen, $this->dpos );
+ }
+
+ return false;
+ }
+
+ /**
+ * @param string $key
+ * @param int $pos
+ * @return bool
+ */
+ protected function match( $key, $pos ) {
+ $buf = $this->read( strlen( $key ), $pos );
+
+ return $buf === $key;
+ }
+
+ protected function findStart() {
+ $this->loop = 0;
+ }
+
+ /**
+ * @throws Exception
+ * @param int $length
+ * @param int $pos
+ * @return string
+ */
+ protected function read( $length, $pos ) {
+ if ( fseek( $this->handle, $pos ) == -1 ) {
+ // This can easily happen if the internal pointers are incorrect
+ throw new Exception(
+ 'Seek failed, file "' . $this->fileName . '" may be corrupted.' );
+ }
+
+ if ( $length == 0 ) {
+ return '';
+ }
+
+ $buf = fread( $this->handle, $length );
+ if ( $buf === false || strlen( $buf ) !== $length ) {
+ throw new Exception(
+ 'Read from CDB file failed, file "' . $this->fileName . '" may be corrupted.' );
+ }
+
+ return $buf;
+ }
+
+ /**
+ * Unpack an unsigned integer and throw an exception if it needs more than 31 bits
+ * @param string $s
+ * @throws Exception
+ * @return mixed
+ */
+ protected function unpack31( $s ) {
+ $data = unpack( 'V', $s );
+ if ( $data[1] > 0x7fffffff ) {
+ throw new Exception(
+ 'Error in CDB file "' . $this->fileName . '", integer too big.' );
+ }
+
+ return $data[1];
+ }
+
+ /**
+ * Unpack a 32-bit signed integer
+ * @param string $s
+ * @return int
+ */
+ protected function unpackSigned( $s ) {
+ $data = unpack( 'va/vb', $s );
+
+ return $data['a'] | ( $data['b'] << 16 );
+ }
+
+ /**
+ * @param string $key
+ * @return bool
+ */
+ protected function findNext( $key ) {
+ if ( !$this->loop ) {
+ $u = Util::hash( $key );
+ $buf = $this->read( 8, ( $u << 3 ) & 2047 );
+ $this->hslots = $this->unpack31( substr( $buf, 4 ) );
+ if ( !$this->hslots ) {
+ return false;
+ }
+ $this->hpos = $this->unpack31( substr( $buf, 0, 4 ) );
+ $this->khash = $u;
+ $u = Util::unsignedShiftRight( $u, 8 );
+ $u = Util::unsignedMod( $u, $this->hslots );
+ $u <<= 3;
+ $this->kpos = $this->hpos + $u;
+ }
+
+ while ( $this->loop < $this->hslots ) {
+ $buf = $this->read( 8, $this->kpos );
+ $pos = $this->unpack31( substr( $buf, 4 ) );
+ if ( !$pos ) {
+ return false;
+ }
+ $this->loop += 1;
+ $this->kpos += 8;
+ if ( $this->kpos == $this->hpos + ( $this->hslots << 3 ) ) {
+ $this->kpos = $this->hpos;
+ }
+ $u = $this->unpackSigned( substr( $buf, 0, 4 ) );
+ if ( $u === $this->khash ) {
+ $buf = $this->read( 8, $pos );
+ $keyLen = $this->unpack31( substr( $buf, 0, 4 ) );
+ if ( $keyLen == strlen( $key ) && $this->match( $key, $pos + 8 ) ) {
+ // Found
+ $this->dlen = $this->unpack31( substr( $buf, 4 ) );
+ $this->dpos = $pos + 8 + $keyLen;
+
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * @param mixed $key
+ * @return bool
+ */
+ protected function find( $key ) {
+ $this->findStart();
+
+ return $this->findNext( $key );
+ }
+}
+
diff --git a/vendor/wikimedia/cdb/src/Util.php b/vendor/wikimedia/cdb/src/Util.php
new file mode 100644
index 00000000..fe19509a
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Util.php
@@ -0,0 +1,100 @@
+<?php
+
+namespace Cdb;
+
+/**
+ * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
+ * appears in PHP 5.3. Changes are:
+ * * Error returns replaced with exceptions
+ * * Exception thrown if sizes or offsets are between 2GB and 4GB
+ * * Some variables renamed
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Common functions for readers and writers
+ */
+class Util {
+ /**
+ * Take a modulo of a signed integer as if it were an unsigned integer.
+ * $b must be less than 0x40000000 and greater than 0
+ *
+ * @param int $a
+ * @param int $b
+ *
+ * @return int
+ */
+ public static function unsignedMod( $a, $b ) {
+ if ( $a & 0x80000000 ) {
+ $m = ( $a & 0x7fffffff ) % $b + 2 * ( 0x40000000 % $b );
+
+ return $m % $b;
+ } else {
+ return $a % $b;
+ }
+ }
+
+ /**
+ * Shift a signed integer right as if it were unsigned
+ * @param int $a
+ * @param int $b
+ * @return int
+ */
+ public static function unsignedShiftRight( $a, $b ) {
+ if ( $b == 0 ) {
+ return $a;
+ }
+ if ( $a & 0x80000000 ) {
+ return ( ( $a & 0x7fffffff ) >> $b ) | ( 0x40000000 >> ( $b - 1 ) );
+ } else {
+ return $a >> $b;
+ }
+ }
+
+ /**
+ * The CDB hash function.
+ *
+ * @param string $s
+ *
+ * @return int
+ */
+ public static function hash( $s ) {
+ $h = 5381;
+ $len = strlen( $s );
+ for ( $i = 0; $i < $len; $i++ ) {
+ $h5 = ( $h << 5 ) & 0xffffffff;
+ // Do a 32-bit sum
+ // Inlined here for speed
+ $sum = ( $h & 0x3fffffff ) + ( $h5 & 0x3fffffff );
+ $h =
+ (
+ ( $sum & 0x40000000 ? 1 : 0 )
+ + ( $h & 0x80000000 ? 2 : 0 )
+ + ( $h & 0x40000000 ? 1 : 0 )
+ + ( $h5 & 0x80000000 ? 2 : 0 )
+ + ( $h5 & 0x40000000 ? 1 : 0 )
+ ) << 30
+ | ( $sum & 0x3fffffff );
+ $h ^= ord( $s[$i] );
+ $h &= 0xffffffff;
+ }
+
+ return $h;
+ }
+}
diff --git a/vendor/wikimedia/cdb/src/Writer.php b/vendor/wikimedia/cdb/src/Writer.php
new file mode 100644
index 00000000..b994ec67
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Writer.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Cdb;
+
+/**
+ * Native CDB file reader and writer.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Write to a CDB file.
+ * Native and pure PHP implementations are provided.
+ * http://cr.yp.to/cdb.html
+ */
+abstract class Writer {
+ /**
+ * The file handle
+ */
+ protected $handle;
+
+ /**
+ * File we'll be writing to when we're done
+ * @var string
+ */
+ protected $realFileName;
+
+ /**
+ * File we write to temporarily until we're done
+ * @var string
+ */
+ protected $tmpFileName;
+
+ /**
+ * Open a writer and return a subclass instance.
+ * The user must have write access to the directory, for temporary file creation.
+ *
+ * @param string $fileName
+ *
+ * @return Writer
+ */
+ public static function open( $fileName ) {
+ return Reader::haveExtension() ?
+ new Writer\DBA( $fileName ) :
+ new Writer\PHP( $fileName );
+ }
+
+ /**
+ * Create the object and open the file
+ *
+ * @param string $fileName
+ */
+ abstract public function __construct( $fileName );
+
+ /**
+ * Set a key to a given value. The value will be converted to string.
+ * @param string $key
+ * @param string $value
+ */
+ abstract public function set( $key, $value );
+
+ /**
+ * Close the writer object. You should call this function before the object
+ * goes out of scope, to write out the final hashtables.
+ */
+ abstract public function close();
+
+ /**
+ * If the object goes out of scope, close it for sanity
+ */
+ public function __destruct() {
+ if ( isset( $this->handle ) ) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Are we running on Windows?
+ * @return bool
+ */
+ protected function isWindows() {
+ return substr( php_uname(), 0, 7 ) == 'Windows';
+ }
+}
diff --git a/vendor/wikimedia/cdb/src/Writer/DBA.php b/vendor/wikimedia/cdb/src/Writer/DBA.php
new file mode 100644
index 00000000..eb525f4c
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Writer/DBA.php
@@ -0,0 +1,63 @@
+<?php
+
+namespace Cdb\Writer;
+use Cdb\Exception;
+use Cdb\Writer;
+
+/**
+ * DBA-based CDB reader/writer
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * Writer class which uses the DBA extension
+ */
+class DBA extends Writer {
+ /**
+ * @throws Exception
+ */
+ public function __construct( $fileName ) {
+ $this->realFileName = $fileName;
+ $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
+ $this->handle = dba_open( $this->tmpFileName, 'n', 'cdb_make' );
+ if ( !$this->handle ) {
+ throw new Exception( 'Unable to open CDB file for write "' . $fileName . '"' );
+ }
+ }
+
+ public function set( $key, $value ) {
+ return dba_insert( $key, $value, $this->handle );
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function close() {
+ if ( isset( $this->handle ) ) {
+ dba_close( $this->handle );
+ }
+ if ( $this->isWindows() ) {
+ unlink( $this->realFileName );
+ }
+ if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
+ throw new Exception( 'Unable to move the new CDB file into place.' );
+ }
+ unset( $this->handle );
+ }
+}
diff --git a/vendor/wikimedia/cdb/src/Writer/PHP.php b/vendor/wikimedia/cdb/src/Writer/PHP.php
new file mode 100644
index 00000000..68c6b760
--- /dev/null
+++ b/vendor/wikimedia/cdb/src/Writer/PHP.php
@@ -0,0 +1,240 @@
+<?php
+
+namespace Cdb\Writer;
+use Cdb\Exception;
+use Cdb\Util;
+use Cdb\Writer;
+
+/**
+ * This is a port of D.J. Bernstein's CDB to PHP. It's based on the copy that
+ * appears in PHP 5.3. Changes are:
+ * * Error returns replaced with exceptions
+ * * Exception thrown if sizes or offsets are between 2GB and 4GB
+ * * Some variables renamed
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
+/**
+ * CDB writer class
+ */
+class PHP extends Writer {
+ protected $hplist;
+
+ protected $numentries;
+
+ protected $pos;
+
+ /**
+ * @param string $fileName
+ */
+ public function __construct( $fileName ) {
+ $this->realFileName = $fileName;
+ $this->tmpFileName = $fileName . '.tmp.' . mt_rand( 0, 0x7fffffff );
+ $this->handle = fopen( $this->tmpFileName, 'wb' );
+ if ( !$this->handle ) {
+ $this->throwException(
+ 'Unable to open CDB file "' . $this->tmpFileName . '" for write.' );
+ }
+ $this->hplist = array();
+ $this->numentries = 0;
+ $this->pos = 2048; // leaving space for the pointer array, 256 * 8
+ if ( fseek( $this->handle, $this->pos ) == -1 ) {
+ $this->throwException( 'fseek failed in file "' . $this->tmpFileName . '".' );
+ }
+ }
+
+ /**
+ * @param string $key
+ * @param string $value
+ */
+ public function set( $key, $value ) {
+ if ( strval( $key ) === '' ) {
+ // DBA cross-check hack
+ return;
+ }
+ $this->addbegin( strlen( $key ), strlen( $value ) );
+ $this->write( $key );
+ $this->write( $value );
+ $this->addend( strlen( $key ), strlen( $value ), Util::hash( $key ) );
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function close() {
+ $this->finish();
+ if ( isset( $this->handle ) ) {
+ fclose( $this->handle );
+ }
+ if ( $this->isWindows() && file_exists( $this->realFileName ) ) {
+ unlink( $this->realFileName );
+ }
+ if ( !rename( $this->tmpFileName, $this->realFileName ) ) {
+ $this->throwException( 'Unable to move the new CDB file into place.' );
+ }
+ unset( $this->handle );
+ }
+
+ /**
+ * @throws Exception
+ * @param string $buf
+ */
+ protected function write( $buf ) {
+ $len = fwrite( $this->handle, $buf );
+ if ( $len !== strlen( $buf ) ) {
+ $this->throwException( 'Error writing to CDB file "' . $this->tmpFileName . '".' );
+ }
+ }
+
+ /**
+ * @throws Exception
+ * @param int $len
+ */
+ protected function posplus( $len ) {
+ $newpos = $this->pos + $len;
+ if ( $newpos > 0x7fffffff ) {
+ $this->throwException(
+ 'A value in the CDB file "' . $this->tmpFileName . '" is too large.' );
+ }
+ $this->pos = $newpos;
+ }
+
+ /**
+ * @param int $keylen
+ * @param int $datalen
+ * @param int $h
+ */
+ protected function addend( $keylen, $datalen, $h ) {
+ $this->hplist[] = array(
+ 'h' => $h,
+ 'p' => $this->pos
+ );
+
+ $this->numentries++;
+ $this->posplus( 8 );
+ $this->posplus( $keylen );
+ $this->posplus( $datalen );
+ }
+
+ /**
+ * @throws Exception
+ * @param int $keylen
+ * @param int $datalen
+ */
+ protected function addbegin( $keylen, $datalen ) {
+ if ( $keylen > 0x7fffffff ) {
+ $this->throwException( 'Key length too long in file "' . $this->tmpFileName . '".' );
+ }
+ if ( $datalen > 0x7fffffff ) {
+ $this->throwException( 'Data length too long in file "' . $this->tmpFileName . '".' );
+ }
+ $buf = pack( 'VV', $keylen, $datalen );
+ $this->write( $buf );
+ }
+
+ /**
+ * @throws Exception
+ */
+ protected function finish() {
+ // Hack for DBA cross-check
+ $this->hplist = array_reverse( $this->hplist );
+
+ // Calculate the number of items that will be in each hashtable
+ $counts = array_fill( 0, 256, 0 );
+ foreach ( $this->hplist as $item ) {
+ ++$counts[255 & $item['h']];
+ }
+
+ // Fill in $starts with the *end* indexes
+ $starts = array();
+ $pos = 0;
+ for ( $i = 0; $i < 256; ++$i ) {
+ $pos += $counts[$i];
+ $starts[$i] = $pos;
+ }
+
+ // Excessively clever and indulgent code to simultaneously fill $packedTables
+ // with the packed hashtables, and adjust the elements of $starts
+ // to actually point to the starts instead of the ends.
+ $packedTables = array_fill( 0, $this->numentries, false );
+ foreach ( $this->hplist as $item ) {
+ $packedTables[--$starts[255 & $item['h']]] = $item;
+ }
+
+ $final = '';
+ for ( $i = 0; $i < 256; ++$i ) {
+ $count = $counts[$i];
+
+ // The size of the hashtable will be double the item count.
+ // The rest of the slots will be empty.
+ $len = $count + $count;
+ $final .= pack( 'VV', $this->pos, $len );
+
+ $hashtable = array();
+ for ( $u = 0; $u < $len; ++$u ) {
+ $hashtable[$u] = array( 'h' => 0, 'p' => 0 );
+ }
+
+ // Fill the hashtable, using the next empty slot if the hashed slot
+ // is taken.
+ for ( $u = 0; $u < $count; ++$u ) {
+ $hp = $packedTables[$starts[$i] + $u];
+ $where = Util::unsignedMod(
+ Util::unsignedShiftRight( $hp['h'], 8 ), $len );
+ while ( $hashtable[$where]['p'] ) {
+ if ( ++$where == $len ) {
+ $where = 0;
+ }
+ }
+ $hashtable[$where] = $hp;
+ }
+
+ // Write the hashtable
+ for ( $u = 0; $u < $len; ++$u ) {
+ $buf = pack( 'vvV',
+ $hashtable[$u]['h'] & 0xffff,
+ Util::unsignedShiftRight( $hashtable[$u]['h'], 16 ),
+ $hashtable[$u]['p'] );
+ $this->write( $buf );
+ $this->posplus( 8 );
+ }
+ }
+
+ // Write the pointer array at the start of the file
+ rewind( $this->handle );
+ if ( ftell( $this->handle ) != 0 ) {
+ $this->throwException( 'Error rewinding to start of file "' . $this->tmpFileName . '".' );
+ }
+ $this->write( $final );
+ }
+
+ /**
+ * Clean up the temp file and throw an exception
+ *
+ * @param string $msg
+ * @throws Exception
+ */
+ protected function throwException( $msg ) {
+ if ( $this->handle ) {
+ fclose( $this->handle );
+ unlink( $this->tmpFileName );
+ }
+ throw new Exception( $msg );
+ }
+}
diff --git a/vendor/wikimedia/cdb/test/CdbTest.php b/vendor/wikimedia/cdb/test/CdbTest.php
new file mode 100644
index 00000000..920fc111
--- /dev/null
+++ b/vendor/wikimedia/cdb/test/CdbTest.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace Cdb\Test;
+use Cdb\Reader;
+use Cdb\Writer;
+
+/**
+ * Test the CDB reader/writer
+ * @covers Cdb\Writer\PHP
+ * @covers Cdb\Writer\DBA
+ */
+class CdbTest extends \PHPUnit_Framework_TestCase {
+ /** @var string */
+ private $phpCdbFile, $dbaCdbFile;
+
+ protected function setUp() {
+ parent::setUp();
+ if ( !Reader::haveExtension() ) {
+ $this->markTestSkipped( 'Native CDB support is not available.' );
+ }
+ $temp = sys_get_temp_dir();
+ if ( !is_writable( $temp ) ) {
+ $this->markTestSkipped( "Temp dir [$temp] isn't writable." );
+ }
+ $this->phpCdbFile = tempnam( $temp, get_class( $this ) . '_' );
+ $this->dbaCdbFile = tempnam( $temp, get_class( $this ) . '_' );
+ }
+
+ /**
+ * Make a random-ish string
+ * @return string
+ */
+ private static function randomString() {
+ $len = mt_rand( 0, 10 );
+ $s = '';
+ for ( $j = 0; $j < $len; $j++ ) {
+ $s .= chr( mt_rand( 0, 255 ) );
+ }
+
+ return $s;
+ }
+
+ public function testCdbWrite() {
+ $w1 = new Writer\PHP( $this->phpCdbFile );
+ $w2 = new Writer\DBA( $this->dbaCdbFile );
+
+ $data = array();
+ for ( $i = 0; $i < 1000; $i++ ) {
+ $key = self::randomString();
+ $value = self::randomString();
+ $w1->set( $key, $value );
+ $w2->set( $key, $value );
+
+ if ( !isset( $data[$key] ) ) {
+ $data[$key] = $value;
+ }
+ }
+
+ $w1->close();
+ $w2->close();
+
+ $this->assertEquals(
+ md5_file( $this->phpCdbFile ),
+ md5_file( $this->dbaCdbFile ),
+ 'same hash'
+ );
+
+ $r1 = new Reader\PHP( $this->phpCdbFile );
+ $r2 = new Reader\DBA( $this->dbaCdbFile );
+
+ foreach ( $data as $key => $value ) {
+ if ( $key === '' ) {
+ // Known bug
+ continue;
+ }
+ $v1 = $r1->get( $key );
+ $v2 = $r2->get( $key );
+
+ $v1 = $v1 === false ? '(not found)' : $v1;
+ $v2 = $v2 === false ? '(not found)' : $v2;
+
+ # cdbAssert( 'Mismatch', $key, $v1, $v2 );
+ $this->cdbAssert( "PHP error", $key, $v1, $value );
+ $this->cdbAssert( "DBA error", $key, $v2, $value );
+ }
+ }
+
+ private function cdbAssert( $msg, $key, $v1, $v2 ) {
+ $this->assertEquals(
+ $v2,
+ $v1,
+ $msg . ', k=' . bin2hex( $key )
+ );
+ }
+}
diff --git a/vendor/wikimedia/composer-merge-plugin/LICENSE b/vendor/wikimedia/composer-merge-plugin/LICENSE
new file mode 100644
index 00000000..55e376b4
--- /dev/null
+++ b/vendor/wikimedia/composer-merge-plugin/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2014 Bryan Davis, Wikimedia Foundation, and contributors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/wikimedia/composer-merge-plugin/README.md b/vendor/wikimedia/composer-merge-plugin/README.md
new file mode 100644
index 00000000..53d64579
--- /dev/null
+++ b/vendor/wikimedia/composer-merge-plugin/README.md
@@ -0,0 +1,80 @@
+[![Latest Stable Version](https://img.shields.io/packagist/v/wikimedia/composer-merge-plugin.svg?style=flat)](https://packagist.org/packages/wikimedia/composer-merge-plugin) [![License](https://img.shields.io/packagist/l/wikimedia/composer-merge-plugin.svg?style=flat)](https://github.com/wikimedia/composer-merge-plugin/blob/master/LICENSE)
+[![Build Status](https://img.shields.io/travis/wikimedia/composer-merge-plugin.svg?style=flat)](https://travis-ci.org/wikimedia/composer-merge-plugin)
+[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/wikimedia/composer-merge-plugin/master.svg?style=flat)](https://scrutinizer-ci.com/g/wikimedia/composer-merge-plugin/?branch=master)
+
+Composer Merge Plugin
+=====================
+
+Merge one or more additional composer.json files at runtime.
+
+Installation
+------------
+```
+$ composer require wikimedia/composer-merge-plugin
+```
+
+Usage
+-----
+
+```
+{
+ "require": {
+ "wikimedia/composer-merge-plugin": "dev-master"
+ },
+ "extra": {
+ "merge-plugin": {
+ "include": [
+ "composer.local.json",
+ "extensions/*/composer.json"
+ ]
+ }
+ }
+}
+```
+
+The `include` key can specify either a single value or an array of values.
+Each value is treated as a glob() pattern identifying additional composer.json
+style configuration files to merge into the configuration for the current
+Composer execution. By default the merge plugin is recursive, if an included
+file also has a "merge-plugin" section it will also be processed. This
+functionality can be disabled by setting `"recurse": false` inside the
+"merge-plugin" section.
+
+The "require", "require-dev", "repositories" and "suggest" sections of the
+found configuration files will be merged into the root package configuration
+as though they were directly included in the top-level composer.json file.
+
+Running tests
+-------------
+```
+$ composer install
+$ composer test
+```
+
+Contributing
+------------
+Bug, feature requests and other issues should be reported to the [GitHub
+project]. We accept code and documentation contributions via Pull Requests on
+GitHub as well.
+
+- [PSR-2 Coding Standard][] is used by the project. The included test
+ configuration uses [PHP Code Sniffer][] to validate the conventions.
+- Tests are encouraged. Our test coverage isn't perfect but we'd like it to
+ get better rather than worse, so please try to include tests with your
+ changes.
+- Keep the documentation up to date. Make sure `README.md` and other
+ relevant documentation is kept up to date with your changes.
+- One pull request per feature. Try to keep your changes focused on solving
+ a single problem. This will make it easier for us to review the change and
+ easier for you to make sure you have updated the necessary tests and
+ documentation.
+
+License
+-------
+Composer Merge plugin is licensed under the MIT license. See the `LICENSE`
+file for more details.
+
+---
+[GitHub project]: https://github.com/wikimedia/composer-merge-plugin
+[PSR-2 Coding Standard]: https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md
+[PHP Code Sniffer]: http://pear.php.net/package/PHP_CodeSniffer
diff --git a/vendor/wikimedia/composer-merge-plugin/composer.json b/vendor/wikimedia/composer-merge-plugin/composer.json
new file mode 100644
index 00000000..5ef429ad
--- /dev/null
+++ b/vendor/wikimedia/composer-merge-plugin/composer.json
@@ -0,0 +1,38 @@
+{
+ "name": "wikimedia/composer-merge-plugin",
+ "description": "Composer plugin to merge multiple composer.json files",
+ "type": "composer-plugin",
+ "license": "MIT",
+ "minimum-stability": "dev",
+ "prefer-stable": true,
+ "require": {
+ "php": ">=5.3.2",
+ "composer-plugin-api": "1.0.0"
+ },
+ "require-dev": {
+ "composer/composer": "1.0.*@dev",
+ "phpunit/phpunit": "~4.0",
+ "jakub-onderka/php-parallel-lint": "~0.8",
+ "squizlabs/php_codesniffer": "~2.1.0",
+ "phpspec/prophecy-phpunit": "~1.0"
+ },
+ "autoload": {
+ "psr-4": {
+ "Wikimedia\\Composer\\": "src/"
+ }
+ },
+ "extra": {
+ "class": "Wikimedia\\Composer\\MergePlugin"
+ },
+ "config": {
+ "optimize-autoloader": true
+ },
+ "scripts": {
+ "test": [
+ "composer validate --no-interaction",
+ "parallel-lint src tests",
+ "phpunit --log-junit=reports/unitreport.xml --coverage-text --coverage-html=reports/coverage --coverage-clover=reports/coverage.xml",
+ "phpcs --encoding=utf-8 --standard=PSR2 --report-checkstyle=reports/checkstyle-phpcs.xml --report-full --extensions=php src/* tests/phpunit/*"
+ ]
+ }
+}
diff --git a/vendor/wikimedia/composer-merge-plugin/src/MergePlugin.php b/vendor/wikimedia/composer-merge-plugin/src/MergePlugin.php
new file mode 100644
index 00000000..04c55886
--- /dev/null
+++ b/vendor/wikimedia/composer-merge-plugin/src/MergePlugin.php
@@ -0,0 +1,411 @@
+<?php
+/**
+ * This file is part of the Composer Merge plugin.
+ *
+ * Copyright (C) 2014 Bryan Davis, Wikimedia Foundation, and contributors
+ *
+ * This software may be modified and distributed under the terms of the MIT
+ * license. See the LICENSE file for details.
+ */
+
+namespace Wikimedia\Composer;
+
+use Composer\Composer;
+use Composer\Config;
+use Composer\EventDispatcher\EventSubscriberInterface;
+use Composer\Installer\InstallerEvent;
+use Composer\Installer\InstallerEvents;
+use Composer\IO\IOInterface;
+use Composer\Json\JsonFile;
+use Composer\Package\BasePackage;
+use Composer\Package\CompletePackage;
+use Composer\Package\Loader\ArrayLoader;
+use Composer\Package\RootPackageInterface;
+use Composer\Package\Version\VersionParser;
+use Composer\Plugin\PluginInterface;
+use Composer\Script\CommandEvent;
+use Composer\Script\ScriptEvents;
+
+/**
+ * Composer plugin that allows merging multiple composer.json files.
+ *
+ * When installed, this plugin will look for a "merge-patterns" key in the
+ * composer configuration's "extra" section. The value of this setting can be
+ * either a single value or an array of values. Each value is treated as
+ * a glob() pattern identifying additional composer.json style configuration
+ * files to merge into the configuration for the current compser execution.
+ *
+ * The "require", "require-dev", "repositories" and "suggest" sections of the
+ * found configuration files will be merged into the root package
+ * configuration as though they were directly included in the top-level
+ * composer.json file.
+ *
+ * If included files specify conflicting package versions for "require" or
+ * "require-dev", the normal Composer dependency solver process will be used
+ * to attempt to resolve the conflict.
+ *
+ * @code
+ * {
+ * "require": {
+ * "wikimedia/composer-merge-plugin": "dev-master"
+ * },
+ * "extra": {
+ * "merge-plugin": {
+ * "include": [
+ * "composer.local.json"
+ * ]
+ * }
+ * }
+ * }
+ * @endcode
+ *
+ * @author Bryan Davis <bd808@bd808.com>
+ */
+class MergePlugin implements PluginInterface, EventSubscriberInterface
+{
+
+ /**
+ * @var Composer $composer
+ */
+ protected $composer;
+
+ /**
+ * @var IOInterface $inputOutput
+ */
+ protected $inputOutput;
+
+ /**
+ * @var ArrayLoader $loader
+ */
+ protected $loader;
+
+ /**
+ * @var array $duplicateLinks
+ */
+ protected $duplicateLinks;
+
+ /**
+ * @var bool $devMode
+ */
+ protected $devMode;
+
+ /**
+ * Whether to recursively include dependencies
+ *
+ * @var bool $recurse
+ */
+ protected $recurse = true;
+
+ /**
+ * Files that have already been processed
+ *
+ * @var string[] $loadedFiles
+ */
+ protected $loadedFiles = array();
+
+ /**
+ * {@inheritdoc}
+ */
+ public function activate(Composer $composer, IOInterface $io)
+ {
+ $this->composer = $composer;
+ $this->inputOutput = $io;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function getSubscribedEvents()
+ {
+ return array(
+ InstallerEvents::PRE_DEPENDENCIES_SOLVING => 'onDependencySolve',
+ ScriptEvents::PRE_INSTALL_CMD => 'onInstallOrUpdate',
+ ScriptEvents::PRE_UPDATE_CMD => 'onInstallOrUpdate',
+ );
+ }
+
+ /**
+ * Handle an event callback for an install or update command by checking
+ * for "merge-patterns" in the "extra" data and merging package contents
+ * if found.
+ *
+ * @param CommandEvent $event
+ */
+ public function onInstallOrUpdate(CommandEvent $event)
+ {
+ $config = $this->readConfig($this->composer->getPackage());
+ if (isset($config['recurse'])) {
+ $this->recurse = (bool)$config['recurse'];
+ }
+ if ($config['include']) {
+ $this->loader = new ArrayLoader();
+ $this->duplicateLinks = array(
+ 'require' => array(),
+ 'require-dev' => array(),
+ );
+ $this->devMode = $event->isDevMode();
+ $this->mergePackages($config);
+ }
+ }
+
+ /**
+ * @param RootPackageInterface $package
+ * @return array
+ */
+ protected function readConfig(RootPackageInterface $package)
+ {
+ $config = array(
+ 'include' => array(),
+ );
+ $extra = $package->getExtra();
+ if (isset($extra['merge-plugin'])) {
+ $config = array_merge($config, $extra['merge-plugin']);
+ if (!is_array($config['include'])) {
+ $config['include'] = array($config['include']);
+ }
+ }
+ return $config;
+ }
+
+ /**
+ * Find configuration files matching the configured glob patterns and
+ * merge their contents with the master package.
+ *
+ * @param array $config
+ */
+ protected function mergePackages(array $config)
+ {
+ $root = $this->composer->getPackage();
+ foreach (array_reduce(
+ array_map('glob', $config['include']),
+ 'array_merge',
+ array()
+ ) as $path) {
+ $this->loadFile($root, $path);
+ }
+ }
+
+ /**
+ * Read a JSON file and merge its contents
+ *
+ * @param RootPackageInterface $root
+ * @param string $path
+ */
+ protected function loadFile($root, $path)
+ {
+ if (in_array($path, $this->loadedFiles)) {
+ $this->debug("Skipping duplicate <comment>$path</comment>...");
+ return;
+ } else {
+ $this->loadedFiles[] = $path;
+ }
+ $this->debug("Loading <comment>{$path}</comment>...");
+ $json = $this->readPackageJson($path);
+ $package = $this->loader->load($json);
+
+ $this->mergeRequires($root, $package);
+ $this->mergeDevRequires($root, $package);
+
+ if (isset($json['repositories'])) {
+ $this->addRepositories($json['repositories'], $root);
+ }
+
+ if ($package->getSuggests()) {
+ $root->setSuggests(array_merge(
+ $root->getSuggests(),
+ $package->getSuggests()
+ ));
+ }
+
+ if ($this->recurse && isset($json['extra']['merge-plugin'])) {
+ $this->mergePackages($json['extra']['merge-plugin']);
+ }
+ }
+
+ /**
+ * Read the contents of a composer.json style file into an array.
+ *
+ * The package contents are fixed up to be usable to create a Package
+ * object by providing dummy "name" and "version" values if they have not
+ * been provided in the file. This is consistent with the default root
+ * package loading behavior of Composer.
+ *
+ * @param string $path
+ * @return array
+ */
+ protected function readPackageJson($path)
+ {
+ $file = new JsonFile($path);
+ $json = $file->read();
+ if (!isset($json['name'])) {
+ $json['name'] = 'merge-plugin/' .
+ strtr($path, DIRECTORY_SEPARATOR, '-');
+ }
+ if (!isset($json['version'])) {
+ $json['version'] = '1.0.0';
+ }
+ return $json;
+ }
+
+ /**
+ * @param RootPackageInterface $root
+ * @param CompletePackage $package
+ */
+ protected function mergeRequires(
+ RootPackageInterface $root,
+ CompletePackage $package
+ ) {
+ $requires = $package->getRequires();
+ if (!$requires) {
+ return;
+ }
+
+ $this->mergeStabilityFlags($root, $requires);
+
+ $root->setRequires($this->mergeLinks(
+ $root->getRequires(),
+ $requires,
+ $this->duplicateLinks['require']
+ ));
+ }
+
+ /**
+ * @param RootPackageInterface $root
+ * @param CompletePackage $package
+ */
+ protected function mergeDevRequires(
+ RootPackageInterface $root,
+ CompletePackage $package
+ ) {
+ $requires = $package->getDevRequires();
+ if (!$requires) {
+ return;
+ }
+
+ $this->mergeStabilityFlags($root, $requires);
+
+ $root->setDevRequires($this->mergeLinks(
+ $root->getDevRequires(),
+ $requires,
+ $this->duplicateLinks['require-dev']
+ ));
+ }
+
+ /**
+ * Extract and merge stability flags from the given collection of
+ * requires.
+ *
+ * @param RootPackageInterface $root
+ * @param array $requires
+ */
+ protected function mergeStabilityFlags(
+ RootPackageInterface $root,
+ array $requires
+ ) {
+ $flags = $root->getStabilityFlags();
+ foreach ($requires as $name => $link) {
+ $name = strtolower($name);
+ $version = $link->getPrettyConstraint();
+ $stability = VersionParser::parseStability($version);
+ $flags[$name] = BasePackage::$stabilities[$stability];
+ }
+ $root->setStabilityFlags($flags);
+ }
+
+ /**
+ * Add a collection of repositories described by the given configuration
+ * to the given package and the global repository manager.
+ *
+ * @param array $repositories
+ * @param RootPackageInterface $root
+ */
+ protected function addRepositories(
+ array $repositories,
+ RootPackageInterface $root
+ ) {
+ $repoManager = $this->composer->getRepositoryManager();
+ $newRepos = array();
+
+ foreach ($repositories as $repoJson) {
+ $this->debug("Adding {$repoJson['type']} repository");
+ $repo = $repoManager->createRepository(
+ $repoJson['type'],
+ $repoJson
+ );
+ $repoManager->addRepository($repo);
+ $newRepos[] = $repo;
+ }
+
+ $root->setRepositories(array_merge(
+ $newRepos,
+ $root->getRepositories()
+ ));
+ }
+
+ /**
+ * Merge two collections of package links and collect duplicates for
+ * subsequent processing.
+ *
+ * @param array $origin Primary collection
+ * @param array $merge Additional collection
+ * @param array &dups Duplicate storage
+ * @return array Merged collection
+ */
+ protected function mergeLinks(array $origin, array $merge, array &$dups)
+ {
+ foreach ($merge as $name => $link) {
+ if (!isset($origin[$name])) {
+ $this->debug("Merging <comment>{$name}</comment>");
+ $origin[$name] = $link;
+ } else {
+ // Defer to solver.
+ $this->debug("Deferring duplicate <comment>{$name}</comment>");
+ $dups[] = $link;
+ }
+ }
+ return $origin;
+ }
+
+ /**
+ * Handle an event callback for pre-dependency solving phase of an install
+ * or update by adding any duplicate package dependencies found during
+ * initial merge processing to the request that will be processed by the
+ * dependency solver.
+ *
+ * @param InstallerEvent $event
+ */
+ public function onDependencySolve(InstallerEvent $event)
+ {
+ if (!$this->duplicateLinks) {
+ return;
+ }
+
+ $request = $event->getRequest();
+ foreach ($this->duplicateLinks['require'] as $link) {
+ $this->debug("Adding dependency <comment>{$link}</comment>");
+ $request->install($link->getTarget(), $link->getConstraint());
+ }
+ if ($this->devMode) {
+ foreach ($this->duplicateLinks['require-dev'] as $link) {
+ $this->debug("Adding dev dependency <comment>{$link}</comment>");
+ $request->install($link->getTarget(), $link->getConstraint());
+ }
+ }
+ }
+
+ /**
+ * Log a debug message
+ *
+ * Messages will be output at the "verbose" logging level (eg `-v` needed
+ * on the Composer command).
+ *
+ * @param string $message
+ */
+ protected function debug($message)
+ {
+ if ($this->inputOutput->isVerbose()) {
+ $this->inputOutput->write(" <info>[merge]</info> {$message}");
+ }
+ }
+}
+// vim:sw=4:ts=4:sts=4:et:
diff --git a/vendor/wikimedia/utfnormal/README.md b/vendor/wikimedia/utfnormal/README.md
new file mode 100644
index 00000000..8e4b0372
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/README.md
@@ -0,0 +1,69 @@
+[![Latest Stable Version](https://poser.pugx.org/wikimedia/utfnormal/v/stable.svg)](https://packagist.org/packages/wikimedia/utfnormal) [![License](https://poser.pugx.org/wikimedia/utfnormal/license.svg)](https://packagist.org/packages/wikimedia/utfnormal)
+
+utfnormal
+=========
+
+utfnormal is a library that contains Unicode normalization routines, including
+both pure PHP implementations and automatic use of the 'intl' PHP extension when
+ present.
+
+The main function to care about is UtfNormal\Validator::cleanUp(). This will
+strip illegal UTF-8 sequences and characters that are illegal in XML, and
+if necessary convert to normalization form C.
+
+If you know the string is already valid UTF-8, you can directly call
+UtfNormal\Validator::toNFC(), toNFK(), or toNFKC(); this will convert a given
+UTF-8 string to Normalization Form C, K, or KC if it's not already such.
+The function assumes that the input string is already valid UTF-8; if there
+are corrupt characters this may produce erroneous results.
+
+Performance is kind of stinky in absolute terms, though it should be speedy
+on pure ASCII text. ;) On text that can be determined quickly to already be
+in NFC it's not too awful but it can quickly get uncomfortably slow,
+particularly for Korean text (the hangul decomposition/composition code is
+extra slow).
+
+Bugs should be filed in [Wikimedia's Phabricator] under the "utfnormal" project.
+
+
+Regenerating data tables
+------------------------
+UtfNormalData.inc and UtfNormalDataK.inc are generated from the Unicode
+Character Database by the script "generate.php". Run "composer generate"
+to rebuild the tables. To fetch updated unicode data from the internet,
+run "composer generate -- --fetch".
+
+
+Testing
+-------
+
+Running "composer test" will run a syntax checker, PHPUnit conformance tests,
+and run some benchmarks using sample texts from Wikipedia. Take all benchmark
+numbers with large grains of salt.
+
+
+PHP module extension
+--------------------
+
+If the 'intl' PHP extension is present, ICU library functions are used which
+are *MUCH* faster than doing this work in pure PHP code.
+
+It is strongly recommended to enable this module if possible:
+http://php.net/manual/en/intro.intl.php
+
+Older versions of this library supported a one-off custom PHP extension,
+which has been dropped. If you were using this, please migrate to the
+intl extension.
+
+
+History
+-------
+This library was first introduced in [MediaWiki 1.3][] ([r4965]). It was
+split out of the MediaWiki codebase and published as an independent library
+during the [MediaWiki 1.25][] development cycle.
+
+---
+[Wikimedia's Phabricator]: https://phabricator.wikimedia.org/maniphest/task/create/?projects=utfnormal
+[MediaWiki 1.3]: https://www.mediawiki.org/wiki/MediaWiki_1.3
+[r4965]: https://www.mediawiki.org/wiki/Special:Code/MediaWiki/4965
+[MediaWiki 1.25]: https://www.mediawiki.org/wiki/MediaWiki_1.25
diff --git a/vendor/wikimedia/utfnormal/scripts/benchmark.php b/vendor/wikimedia/utfnormal/scripts/benchmark.php
new file mode 100644
index 00000000..51cb2aa9
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/benchmark.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * Approximate benchmark for some basic operations.
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
+
+use UtfNormal\Validator;
+
+if ( PHP_SAPI != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
+require_once dirname( __DIR__ ) . '/vendor/autoload.php';
+
+define( 'BENCH_CYCLES', 5 );
+
+$testfiles = array(
+ __DIR__ . '/testdata/washington.txt' => 'English text',
+ __DIR__ . '/testdata/berlin.txt' => 'German text',
+ __DIR__ . '/testdata/bulgakov.txt' => 'Russian text',
+ __DIR__ . '/testdata/tokyo.txt' => 'Japanese text',
+ __DIR__ . '/testdata/young.txt' => 'Korean text'
+);
+$normalizer = new Validator;
+Validator::loadData();
+foreach ( $testfiles as $file => $desc ) {
+ benchmarkTest( $normalizer, $file, $desc );
+}
+
+# -------
+
+function benchmarkTest( &$u, $filename, $desc ) {
+ print "Testing $filename ($desc)...\n";
+ $data = file_get_contents( $filename );
+ $forms = array(
+# 'placebo',
+ 'cleanUp',
+ 'toNFC',
+# 'toNFKC',
+# 'toNFD', 'toNFKD',
+ 'NFC',
+# 'NFKC',
+# 'NFD', 'NFKD',
+ array( 'fastDecompose', 'fastCombiningSort', 'fastCompose' ),
+# 'quickIsNFC', 'quickIsNFCVerify',
+ );
+
+ foreach ( $forms as $form ) {
+ if ( is_array( $form ) ) {
+ $str = $data;
+ foreach ( $form as $step ) {
+ $str = benchmarkForm( $u, $str, $step );
+ }
+ } else {
+ benchmarkForm( $u, $data, $form );
+ }
+ }
+}
+
+function benchmarkForm( &$u, &$data, $form ) {
+ #$start = microtime( true );
+ for ( $i = 0; $i < BENCH_CYCLES; $i++ ) {
+ $start = microtime( true );
+ $out = $u->$form( $data, Validator::$utfCanonicalDecomp );
+ $deltas[] = ( microtime( true ) - $start );
+ }
+ #$delta = (microtime( true ) - $start) / BENCH_CYCLES;
+ sort( $deltas );
+ $delta = $deltas[0]; # Take shortest time
+
+ $rate = intval( strlen( $data ) / $delta );
+ $same = ( 0 == strcmp( $data, $out ) );
+
+ printf( " %20s %6.1fms %12s bytes/s (%s)\n",
+ $form,
+ $delta * 1000.0,
+ number_format( $rate ),
+ ( $same ? 'no change' : 'changed' ) );
+
+ return $out;
+}
diff --git a/vendor/wikimedia/utfnormal/scripts/generate.php b/vendor/wikimedia/utfnormal/scripts/generate.php
new file mode 100644
index 00000000..905f0a81
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/generate.php
@@ -0,0 +1,273 @@
+<?php
+/**
+ * This script generates UniNormalData.inc from the Unicode Character Database
+ * and supplementary files.
+ *
+ * Copyright (C) 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
+
+use UtfNormal\Utils;
+
+if ( PHP_SAPI != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
+require_once dirname( __DIR__ ) . '/vendor/autoload.php';
+
+function download( $file, $url ) {
+ print "Downloading data from $url...\n";
+ $fp = fopen( $file, 'w+' );
+ $ch = curl_init( $url );
+ curl_setopt( $ch, CURLOPT_FILE, $fp );
+ curl_exec( $ch );
+ curl_close( $ch );
+ fclose( $fp );
+}
+
+function getFilePointer( $file, $url ) {
+ if ( in_array( '--fetch', $_SERVER['argv'] ) ) {
+ download( $file, $url );
+ } elseif ( !file_exists( $file ) ) {
+ print "Can't open $file for reading.\n";
+ print "If necessary, fetch this file from the internet:\n";
+ print "$url\n";
+ print "Or re-run this script with --fetch\n";
+ exit( -1 );
+ }
+
+ $fp = fopen( $file, "rt" );
+ if ( !$fp ) {
+ // Eh?
+ print "Can't open $file for reading.\n";
+ exit( -1 );
+ }
+
+ return $fp;
+}
+
+
+$in = getFilePointer(
+ __DIR__ . "/data/DerivedNormalizationProps.txt",
+ 'http://www.unicode.org/Public/UNIDATA/DerivedNormalizationProps.txt'
+);
+print "Initializing normalization quick check tables...\n";
+$checkNFC = array();
+while ( false !== ( $line = fgets( $in ) ) ) {
+ $matches = array();
+ if ( preg_match(
+ '/^([0-9A-F]+)(?:..([0-9A-F]+))?\s*;\s*(NFC_QC)\s*;\s*([MN])/',
+ $line,
+ $matches )
+ ) {
+ list( $junk, $first, $last, $prop, $value ) = $matches;
+ #print "$first $last $prop $value\n";
+ if ( !$last ) {
+ $last = $first;
+ }
+
+ $lastInDecimal = hexdec( $last );
+ for ( $i = hexdec( $first ); $i <= $lastInDecimal; $i++ ) {
+ $char = Utils::codepointToUtf8( $i );
+ $checkNFC[$char] = $value;
+ }
+ }
+}
+fclose( $in );
+
+$in = getFilePointer(
+ __DIR__ . "/data/CompositionExclusions.txt",
+ 'http://www.unicode.org/Public/UNIDATA/CompositionExclusions.txt'
+);
+$exclude = array();
+while ( false !== ( $line = fgets( $in ) ) ) {
+ if ( preg_match( '/^([0-9A-F]+)/i', $line, $matches ) ) {
+ $codepoint = $matches[1];
+ $source = Utils::codepointToUtf8( hexdec( $codepoint ) );
+ $exclude[$source] = true;
+ }
+}
+fclose( $in );
+
+$in = getFilePointer(
+ __DIR__ . "/data/UnicodeData.txt",
+ 'http://www.unicode.org/Public/UNIDATA/UnicodeData.txt'
+);
+$compatibilityDecomp = array();
+$canonicalDecomp = array();
+$canonicalComp = array();
+$combiningClass = array();
+$total = 0;
+$compat = 0;
+$canon = 0;
+
+print "Reading character definitions...\n";
+while ( false !== ( $line = fgets( $in ) ) ) {
+ $columns = explode( ';', $line );
+ $codepoint = $columns[0];
+ $name = $columns[1];
+ $canonicalCombiningClass = $columns[3];
+ $decompositionMapping = $columns[5];
+
+ $source = Utils::codepointToUtf8( hexdec( $codepoint ) );
+
+ if ( $canonicalCombiningClass != 0 ) {
+ $combiningClass[$source] = intval( $canonicalCombiningClass );
+ }
+
+ if ( $decompositionMapping === '' ) continue;
+ if ( preg_match( '/^<(.+)> (.*)$/', $decompositionMapping, $matches ) ) {
+ # Compatibility decomposition
+ $canonical = false;
+ $decompositionMapping = $matches[2];
+ $compat++;
+ } else {
+ $canonical = true;
+ $canon++;
+ }
+ $total++;
+ $dest = Utils::hexSequenceToUtf8( $decompositionMapping );
+
+ $compatibilityDecomp[$source] = $dest;
+ if ( $canonical ) {
+ $canonicalDecomp[$source] = $dest;
+ if ( empty( $exclude[$source] ) ) {
+ $canonicalComp[$dest] = $source;
+ }
+ }
+ #print "$codepoint | $canonicalCombiningClasses | $decompositionMapping\n";
+}
+fclose( $in );
+
+print "Recursively expanding canonical mappings...\n";
+$changed = 42;
+$pass = 1;
+while ( $changed > 0 ) {
+ print "pass $pass\n";
+ $changed = 0;
+ foreach ( $canonicalDecomp as $source => $dest ) {
+ $newDest = preg_replace_callback(
+ '/([\xc0-\xff][\x80-\xbf]+)/',
+ 'callbackCanonical',
+ $dest );
+ if ( $newDest === $dest ) continue;
+ $changed++;
+ $canonicalDecomp[$source] = $newDest;
+ }
+ $pass++;
+}
+
+print "Recursively expanding compatibility mappings...\n";
+$changed = 42;
+$pass = 1;
+while ( $changed > 0 ) {
+ print "pass $pass\n";
+ $changed = 0;
+ foreach ( $compatibilityDecomp as $source => $dest ) {
+ $newDest = preg_replace_callback(
+ '/([\xc0-\xff][\x80-\xbf]+)/',
+ 'callbackCompat',
+ $dest );
+ if ( $newDest === $dest ) continue;
+ $changed++;
+ $compatibilityDecomp[$source] = $newDest;
+ }
+ $pass++;
+}
+
+print "$total decomposition mappings ($canon canonical, $compat compatibility)\n";
+
+$out = fopen( dirname( __DIR__ ) . "/src/UtfNormalData.inc", "wt" );
+if ( $out ) {
+ $serCombining = Utils::escapeSingleString( serialize( $combiningClass ) );
+ $serComp = Utils::escapeSingleString( serialize( $canonicalComp ) );
+ $serCanon = Utils::escapeSingleString( serialize( $canonicalDecomp ) );
+ $serCheckNFC = Utils::escapeSingleString( serialize( $checkNFC ) );
+ $outdata = "<" . "?php
+/**
+ * This file was automatically generated -- do not edit!
+ * Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
+ */
+// @codingStandardsIgnoreFile
+
+UtfNormal\Validator::\$utfCombiningClass = unserialize( '$serCombining' );
+UtfNormal\Validator::\$utfCanonicalComp = unserialize( '$serComp' );
+UtfNormal\Validator::\$utfCanonicalDecomp = unserialize( '$serCanon' );
+UtfNormal\Validator::\$utfCheckNFC = unserialize( '$serCheckNFC' );
+\n";
+ fputs( $out, $outdata );
+ fclose( $out );
+ print "Wrote out UtfNormalData.inc\n";
+} else {
+ print "Can't create file UtfNormalData.inc\n";
+ exit( -1 );
+}
+
+$out = fopen( dirname( __DIR__ ) . "/src/UtfNormalDataK.inc", "wt" );
+if ( $out ) {
+ $serCompat = Utils::escapeSingleString( serialize( $compatibilityDecomp ) );
+ $outdata = "<" . "?php
+/**
+ * This file was automatically generated -- do not edit!
+ * Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
+ */
+// @codingStandardsIgnoreFile
+
+UtfNormal\Validator::\$utfCompatibilityDecomp = unserialize( '$serCompat' );
+\n";
+ fputs( $out, $outdata );
+ fclose( $out );
+ print "Wrote out UtfNormalDataK.inc\n";
+ exit( 0 );
+} else {
+ print "Can't create file UtfNormalDataK.inc\n";
+ exit( -1 );
+}
+
+# ---------------
+
+function callbackCanonical( $matches ) {
+ // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
+ global $canonicalDecomp;
+ // @codingStandardsIgnoreEnd
+
+ if ( isset( $canonicalDecomp[$matches[1]] ) ) {
+ return $canonicalDecomp[$matches[1]];
+ }
+
+ return $matches[1];
+}
+
+function callbackCompat( $matches ) {
+ // @codingStandardsIgnoreStart MediaWiki.NamingConventions.ValidGlobalName.wgPrefix
+ global $compatibilityDecomp;
+ // @codingStandardsIgnoreEnd
+
+ if ( isset( $compatibilityDecomp[$matches[1]] ) ) {
+ return $compatibilityDecomp[$matches[1]];
+ }
+
+ return $matches[1];
+}
diff --git a/vendor/wikimedia/utfnormal/scripts/memstress.php b/vendor/wikimedia/utfnormal/scripts/memstress.php
new file mode 100644
index 00000000..f444e34d
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/memstress.php
@@ -0,0 +1,103 @@
+<?php
+/**
+ * Approximate benchmark for some basic operations.
+ * Runs large chunks of text through cleanup with a lowish memory limit,
+ * to test regression on mem usage (bug 28146)
+ *
+ * Copyright © 2004-2011 Brion Vibber <brion@wikimedia.org>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
+
+use UtfNormal\Validator;
+
+if ( PHP_SAPI != 'cli' ) {
+ die( "Run me from the command line please.\n" );
+}
+
+require_once dirname( __DIR__ ) . '/vendor/autoload.php';
+
+define( 'BENCH_CYCLES', 1 );
+define( 'BIGSIZE', 1024 * 1024 * 10 ); // 10m
+ini_set( 'memory_limit', BIGSIZE + 120 * 1024 * 1024 );
+
+$testfiles = array(
+ 'testdata/washington.txt' => 'English text',
+ 'testdata/berlin.txt' => 'German text',
+ 'testdata/bulgakov.txt' => 'Russian text',
+ 'testdata/tokyo.txt' => 'Japanese text',
+ 'testdata/young.txt' => 'Korean text'
+);
+$normalizer = new Validator;
+Validator::loadData();
+foreach ( $testfiles as $file => $desc ) {
+ benchmarkTest( $normalizer, $file, $desc );
+}
+
+# -------
+
+function benchmarkTest( &$u, $filename, $desc ) {
+ print "Testing $filename ($desc)...\n";
+ $data = file_get_contents( $filename );
+ $all = $data;
+ while ( strlen( $all ) < BIGSIZE ) {
+ $all .= $all;
+ }
+ $data = $all;
+ echo "Data is " . strlen( $data ) . " bytes.\n";
+ $forms = array(
+ 'quickIsNFCVerify',
+ 'cleanUp',
+ );
+
+ foreach ( $forms as $form ) {
+ if ( is_array( $form ) ) {
+ $str = $data;
+ foreach ( $form as $step ) {
+ $str = benchmarkForm( $u, $str, $step );
+ }
+ } else {
+ benchmarkForm( $u, $data, $form );
+ }
+ }
+}
+
+function benchmarkForm( &$u, &$data, $form ) {
+ #$start = microtime( true );
+ for ( $i = 0; $i < BENCH_CYCLES; $i++ ) {
+ $start = microtime( true );
+ $out = $u->$form( $data, Validator::$utfCanonicalDecomp );
+ $deltas[] = ( microtime( true ) - $start );
+ }
+ #$delta = (microtime( true ) - $start) / BENCH_CYCLES;
+ sort( $deltas );
+ $delta = $deltas[0]; # Take shortest time
+
+ $rate = intval( strlen( $data ) / $delta );
+ $same = ( 0 == strcmp( $data, $out ) );
+
+ printf( " %20s %6.1fms %12s bytes/s (%s)\n",
+ $form,
+ $delta * 1000.0,
+ number_format( $rate ),
+ ( $same ? 'no change' : 'changed' ) );
+
+ return $out;
+}
diff --git a/vendor/wikimedia/utfnormal/scripts/testdata/berlin.txt b/vendor/wikimedia/utfnormal/scripts/testdata/berlin.txt
new file mode 100644
index 00000000..0fef4d23
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/testdata/berlin.txt
@@ -0,0 +1,454 @@
+''Dieser Artikel beschäftigt sich mit Berlin, der Hauptstadt Deutschlands. Andere Namensträger finden sich unter [[Berlin (Begriffsklärung)]].''
+----
+{| border="0" cellpadding="2" cellspacing="1" style="float:right; empty-cells:show; margin-left:1em; margin-bottom:0.5em; background:#FFDEAD;"
+|+<font size="+1">'''Land Berlin'''</font><br />
+! colspan="2" bgcolor="#FFDEAD" | Landes- und Dienstflagge
+|-bgcolor="#FFFFFF"
+| colspan="2" align="center" bgcolor="#EFEFEF" |[[bild:Flagge_Berlin.png|150px|Landesflagge Berlins]] [[bild:Dienstflagge Berlin.png|150px|Dienstflagge des Landes Berlin]]<br />
+Landes- und Dienstflagge des Landes Berlin
+|- bgcolor="#FFDEAD"
+! Stadtwappen !! Karte
+|-bgcolor="#FFFFFF"
+! align="center" | [[Bild:Berlin Stadtwappen.png|center|140px|Wappen der Stadt Berlin]] || [[Bild:Karte berlin in deutschland.png|center|140px|Karte des Landes Berlin in Deutschland]]
+|-
+!colspan="2" bgcolor="#ffdead" | Basisdaten
+|-bgcolor="#FFFFFF"
+|[[Fläche]]: || [[Größenordnung (Fläche)|891,69 km²]]
+|-bgcolor="#FFFFFF"
+|[[Einwohner]]: || 3.388.477 <small>(12/2003)</small>
+|-bgcolor="#FFFFFF"
+|[[Bevölkerungsdichte]]: || 3.800 Einwohner/km²
+|-bgcolor="#FFFFFF"
+|Schulden: || 15.906 € pro Einwohner <br />''<small>(April 2004) [http://www.statistik-berlin.de/statistiken/oefffinanzen/schulden.htm]</small>''
+|-bgcolor="#FFFFFF"
+|Schulden gesamt: || 53,9 Mrd. € ''<small>(April 2004) [http://www.steuerzahler-berlin.de/]</small>''
+|-bgcolor="#FFFFFF"
+|[[Höhe]]: || 34 m ü. [[Normalnull|NN]]
+|-bgcolor="#FFFFFF"
+|[[Postleitzahl]]en: || 10001-14199
+|-bgcolor="#FFFFFF"
+|[[Telefonvorwahl|Vorwahlen]]: || 030
+|-bgcolor="#FFFFFF"
+|td valign="top" | [[Geografische Lage]]: || 52°&nbsp;31′ n.Br. <br />13°&nbsp;24′ ö.L.
+|-bgcolor="#FFFFFF"
+|[[KFZ-Kennzeichen]]: || <tt>B</tt>
+|-bgcolor="#FFFFFF"
+|[[Amtlicher Gemeindeschlüssel|Gemeindeschlüssel]]: || 11 1 00 000
+|-bgcolor="#FFFFFF"
+|Gliederung: || [[Orte in Berlin|12 Bezirke, 90 Stadtteile]]
+|-bgcolor="#FFFFFF"
+|[[ISO 3166-2:DE|ISO 3166-2]]: || <tt>DE-BE</tt>
+|-bgcolor="#FFFFFF"
+|Webseite: || http://www.berlin.de
+|-
+!colspan="2" bgcolor="#ffdead" | Politik
+|-bgcolor="#FFFFFF"
+|Reg. [[Bürgermeister]]: || [[Klaus Wowereit]] ([[Sozialdemokratische Partei Deutschlands|SPD]])
+|-bgcolor="#FFFFFF"
+|Reg. [[Politische Partei|Parteien]]: || [[Sozialdemokratische Partei Deutschlands|SPD]] und [[PDS]]
+|-bgcolor="#FFFFFF"
+|td valign="top" | [[Sitzverteilung in den deutschen Landesparlamenten|Sitzverteilung im<br />Abgeordnetenhaus]]<br />(141 Sitze): || td valign="top" | [[Sozialdemokratische Partei Deutschlands|SPD]] 44<br />[[CDU]] 35<br />[[PDS]] 33<br />[[FDP (Deutschland)|FDP]] 15<br />[[Bündnis 90/Die Grünen|B90/Grüne]] 14
+|-bgcolor="#FFFFFF"
+|letzte Wahl: || [[21. Oktober]] [[2001]]
+|-bgcolor="#FFFFFF"
+|nächste Wahl: || [[2006]]
+|-
+! colspan="2" bgcolor="#ffdead" | Parlamentarische Vertretung
+|-bgcolor="#FFFFFF"
+| Stimmen im [[Bundesrat (Deutschland)|Bundesrat]]: || 4
+|}
+'''Berlin''' ist als [[Stadtstaat]] ein [[Bundesland (Deutschland)|Land]] in der [[Bundesrepublik Deutschland]] und die größte Stadt Deutschlands: sie ist die bevölkerungsreichste Stadt ([[2003]]: 3.388.477 Einwohner) und mit einer Fläche von 89.169 ha auch die [[Liste der flächengrößten Städte Deutschlands|flächengrößte]] Stadt Deutschlands.
+
+Seit [[1990]] ist '''Berlin''' die [[Bundeshauptstadt]] der [[Bundesrepublik Deutschland]] nach der [[Deutsche Wiedervereinigung|Wiedervereinigung]]. Nach dem Fall der [[Berliner Mauer]] wuchs Berlin mit dem Umland zusammen, zur [[Megastadt]] der [[Metropolregion Berlin/Brandenburg]].
+
+==Allgemeines==
+Das Land Berlin ist gänzlich vom Land [[Brandenburg]] umgeben und liegt im Osten Deutschlands, etwa 70 km westlich der [[Polen|polnischen]] Grenze. Die Stadt ist einer der [[Ballungsgebiet|Verdichtungsräume]] der Bundesrepublik. [[1996]] fand eine Volksabstimmung über eine Länderfusion mit Brandenburg statt, welche jedoch scheiterte. Da man trotz dieses Ergebnisses auf politischer Seite weiterhin von der Notwendigkeit der Fusion überzeugt ist, strebt man nun eine erneute Volksabstimmung über diese Frage an.
+
+Vor der [[Wiedervereinigung]] der beiden deutschen Staaten [[Bundesrepublik Deutschland]] (''BRD'', West) und [[Deutsche Demokratische Republik]] (''DDR'', Ost) im Jahr [[1990]] war [[Bonn]] am [[Rhein]] die provisorische Hauptstadt der Bundesrepublik Deutschland, und [[Ost-Berlin]] war die Hauptstadt der ''DDR'' (amtlich: ''Berlin, Hauptstadt der DDR'', oder amtlich in der ''BRD'': ''Berlin (-Ost)''). Der Westteil Berlins ([[West-Berlin]], oder amtlich: ''Berlin (-West)'', amtlich in der DDR: ''Selbständige politische Einheit Westberlin'') nahm eine Sonderstellung zwischen den beiden deutschen Staaten ein.
+
+Mit der Wiedervereinigung am [[3. Oktober]] [[1990]] wurde Berlin wieder Hauptstadt Deutschlands. Im so genannten [[Hauptstadtbeschluss]] entschied der [[Deutscher Bundestag|Deutsche Bundestag]] am [[20. Juni]] [[1991]], dass Berlin auch Regierungs- und Parlamentssitz des vereinigten Deutschlands werden soll.
+
+== Geografie ==
+
+[[Bild:Luftbild_Berlin.jpg|thumb|Luftbild]]
+
+Berlin befindet sich in [[Eiszeit|eiszeitlich]] geprägter Landschaft im Warschau-[[Berliner Urstromtal]] zwischen den Hochebenen des [[Barnim]] und des [[Teltow (Landschaft)|Teltow]]. Das historische Zentrum Berlin liegt an der schmalsten Stelle des von der [[Spree]] in ost-westlicher Richtung durchflossenen Urstromtals. Im westlichsten [[#Bezirke|Bezirk]] [[Spandau]] mündet die Spree in die [[Havel]], die den Westen Berlins in Nord-Süd Richtung durchfließt. Der Flusslauf der Havel ähnelt dabei oft einer Seenlandschaft, die größten Ausbuchtungen bilden der [[Tegeler See]] und der [[Großer Wannsee|Große Wannsee]].
+
+Wesentliche Teile des heutigen Berlins liegen auf den beiden Hochebenen: große Teile der Bezirke [[Reinickendorf]] und [[Pankow]] liegen auf dem Barnim, während der Hauptteil der Bezirke [[Charlottenburg-Wilmersdorf]], [[Steglitz-Zehlendorf]], [[Tempelhof-Schöneberg]] und [[Neukölln]] auf dem Teltow gelegen ist.
+
+Die höchsten Erhebungen Berlins sind mit 120 m über NN der aus Trümmerschutt des [[Zweiter Weltkrieg|Zweiten Weltkriegs]] künstlich aufgeschüttete [[Teufelsberg]] im Bezirk [[Charlottenburg-Wilmersdorf]] und mit 115 m die [[Müggelberge]] im Bezirk [[Treptow-Köpenick]].
+
+=== Bezirke ===
+[[Bild:berlin.bezirke.png|thumb|Bezirke Berlins]]
+
+Nach dem Gebietsreformgesetz vom [[10. Juni]] [[1998]] wurde die Zahl der Berliner Verwaltungsbezirke zum [[1. Januar]] [[2001]] von 23 auf 12 reduziert.
+Berlin ist seitdem gegliedert in die Bezirke [[Berlin-Mitte|Mitte]], [[Friedrichshain-Kreuzberg]], [[Pankow]], [[Charlottenburg-Wilmersdorf]], [[Spandau]], [[Steglitz-Zehlendorf]], [[Tempelhof-Schöneberg]], [[Neukölln]], [[Treptow-Köpenick]], [[Marzahn-Hellersdorf]], [[Berlin-Lichtenberg|Lichtenberg]], [[Reinickendorf]].
+
+Weitere Differenzierung in [[Liste der Orte in Berlin]], siehe auch [[Berliner Straßen]] und Plätze.
+
+== Geschichte ==
+
+''Hauptartikel:'' [[Geschichte Berlins]]
+
+Die Geschichte Berlins beginnt nicht erst mit der ersten urkundlichen Erwähnung, sondern bereits mit der Vor- und [[Frühgeschichte]] des Berliner Raumes.
+Zeugnisse dieser frühen Phase der Besiedlung sind vor allem im Museum für Vor- und Frühgeschichte sowie als lebensechte Nachbildung im Museumsdorf Düppel zu sehen. Hier werden auch mittelalterliche handwerkliche Techniken vorgeführt.
+[[Bild:Reichstag.JPG|thumb|left|Der Reichstag]]
+[[Bild:Berliner Rathaus gross.jpeg|thumb|Berliner Rathaus (''Rotes Rathaus'')]]
+
+Funde von [[Feuerstein]]en und bearbeiteten Knochen in der Gegend lassen auf eine Besiedlung seit etwa 60000 v. Chr. schließen. Im [[9. Jahrtausend v. Chr.]] siedeln an der [[Spree]] und an der [[Dahme (Fluss)|Dahme]] [[Jagd|Jäger]] und [[Fischer]], die [[Pfeil (Geschoss)|Pfeil]]spitzen, Schaber und Feuersteinbeile hinterlassen. Aus dem [[7. Jahrtausend v. Chr.]] wurde eine Maske gefunden, die wohl als Jagdzauber diente.
+
+Im [[3. Jahrtausend v. Chr.]] bilden sich Kulturen mit [[Ackerbau]] und [[Viehzucht]], die handgefertigte [[Keramik]]en und [[Vorratsspeicher]] benutzen.
+
+Seit dem [[6. Jahrhundert v. Chr.]] siedeln sich verstärkt [[Germanen]] an:
+in historischen Quellen tauchen für sie die Stammesbezeichnungen
+[[Semnonen]] (Teilstamm der [[Sweben]]) und die [[Burgunder (Volk)|Burgunder]] auf.
+
+Später ziehen vom Berliner Raum aus Germanen in den süddeutschen Raum. Im Berliner Raum nimmt daher die Besiedlungsdichte ab, er bleibt aber germanisch besiedelt. Ab dem [[6. Jahrhundert]] strömen Slawenstämme in die [[Lausitz]]er Gegend und um das Jahr [[720]] auch in den Berliner Raum. Sie lassen sich auch an bisher unbesiedelten Standorten nieder und unterscheiden sich durch Standortwahl und Wirtschaftsweise von den Germanen.
+
+Ab dem [[12. Jahrhundert]] beginnt die Ostbesiedlung des Kaiserreichs.
+Auf den trockenen Flächen des [[Urstromtal]]s zwischen dem [[Teltow]] und dem [[Barnim]] wird eine Furt über die Spree besiedelt. Auf der rechten Uferseite entsteht Berlin, auf einer Spreeinsel [[Cölln]].
+
+'''Die wichtigsten historischen Daten:'''
+[[Bild:Kuppel des Reichstags.JPG|thumb|Der Reichstag mit Kuppel (vom Brandenburger Tor aus)]]
+:[[1197]] erste urkundliche Erwähnung [[Spandau]]s, das [[1232]] [[Stadtrecht]] erhält (seit 1920 Teil von Berlin)
+:[[1209]] erste urkundliche Erwähnung [[Köpenick]]s (seit 1920 Teil von Berlin)
+:um [[1230]] Verleihung des Stadtrechts für Berlin
+:[[1237]] erste urkundliche Erwähnung von Cölln
+:[[1244]] erste urkundliche Erwähnung von Berlin
+:[[1307]] Berlin und Cölln erhalten eine gemeinsame Verwaltung
+:[[1448]] "[[Berliner Unwille]]": Proteste der Bevölkerung gegen den brandenburgischen Kurfürsten [[Friedrich II. (Brandenburg)|Friedrich II. Eisenzahn]] und dessen Schlossneubau
+:[[1451]] Berlin wird Residenzstadt der [[Mark Brandenburg|brandenburgischen]] Markgrafen und Kurfürsten
+:[[1539]] Einführung der [[Reformation]] in Berlin
+:[[1567]] "[[Knüppelkrieg]]" zwischen Berlin und Spandau
+:ab [[1641]] mehrfache Stadterweiterungen, es entstehen die Vorstädte Friedrichswerder, Dorotheenstadt und Friedrichstadt
+:[[1701]] Berlin wird durch die Krönung des brandenburgischen Kurfürsten zum preußischen König [[Friedrich I. (Preußen)|Friedrich I.]] zur Hauptstadt des Königreichs [[Preußen]]
+:[[1709]] Die fünf bis dahin unabhängigen Städte Berlin, Cölln, Friedrichswerder, Dorotheenstadt und Friedrichstadt werden zur "königlichen Haupt- und Residenzstadt Berlin" vereinigt. Schon bald entstehen vor den Toren der Stadt neue Vorstädte.
+:[[1848]] [[Märzrevolution]] mit Barrikadenkämpfen
+:[[1861]] Das Stadtgebiet wird durch die Eingemeindung von Wedding, [[Moabit]], Tempelhofer und Schöneberger Vorstadt erweitert.
+:[[1871]] Berlin wird Hauptstadt des neu entstandenen Deutschen Reiches
+:[[1873]] Beginn des Baues des im Wesentlichen noch heute genutzten Kanalisationssystems (abgeschlossen 1893)
+:[[1918]] Als Ergebnis der [[Novemberrevolution]] wird in Berlin kurz hintereinander jeweils von [[Philipp Scheidemann]] und [[Karl Liebknecht]] die [[Republik]] ausgerufen.
+:[[1920]] umfangreiche Eingemeindung: Berlin wurde mit 7 weiteren Städten, 59 Landgemeinden und 28 Gutsbezirken zu "[[Groß-Berlin]]"
+:[[1933]] Nach der Ernennung [[Hitler]]s zum deutschen Reichskanzler wird Berlin zur Machtzentrale der [[Nationalsozialisten]]; diese richten die ersten "wilden Konzentrationslager" ein; Ende des Rechtsstaates; Terror gegen Kommunisten, Sozialdemokraten, entschiedene Christen und Juden
+:[[1936]] [[Olympische Sommerspiele 1936|Olympische Sommerspiele]] in Berlin
+:[[1937]] große 700-Jahr-Feiern in Berlin, die von den Nationalsozialisten als Propagandaveranstaltung genutzt werden
+: [[1938]] Vom 9. bis 10. November brannten infolge der "Reichspogromnacht" die Synagogen, jüdische Geschäfte und Wohnungen wurden demoliert, viele Juden verhaftet
+: In der Zeit des Nationalsozialismus bestand in der Nähe Berlins, in Uckermark, ein Jugendkonzentratonslager für Mädchen und junge Frauen, innerhalb Berlins bestanden mehrere kleine KZ-Außenstellen
+: [[1939]] Von Berlin aus wird der Zweite Weltkrieg ausgelöst, Hitler erlässt den Befehl zur "Aktion T 4" (Euthanasie-Befehl)
+: [[1940]]-[[1945]] Durch [[Flächenbombardement]]s der Alliierten sterben über 50.000 Berliner, mehr als 1,5 Millionen werden obdachlos.
+:[[1945]] das durch den Zweiten Weltkrieg zerstörte Berlin wird nach dem [[Zweiter Weltkrieg|Zweiten Weltkrieg]] "[[Vier-Sektoren-Stadt]]", die Versorgung der Bevölkerung mit Nahrungsmitteln und Brennholz ist katastrophal, das S-Bahnnetz und die Straßenbahn sind zerstört
+:[[1948]]/[[1949]] Blockade der westlichen Sektoren durch die sowjetischen Streitkräfte, [[Berliner Luftbrücke]] zur Versorgung der Bevölkerung mit Lebensmitteln und Gütern
+:[[1945]]/[[1949]] politische Spaltung Berlins im [[Ost-West-Konflikt]]
+:[[1961]] Bau der [[Berliner Mauer]] am [[13. August]]
+:[[1963]] Bei einem Besuch Westberlins spricht US-Präsident [[John F. Kennedy]] den berühmt gewordenen Satz "Ich bin ein Berliner".
+:[[1987]] große 750-Jahr-Feiern in beiden Teilen der Stadt
+:[[1989]] die Wende in der [[Deutsche Demokratische Republik|DDR]], Fall der [[Berliner Mauer|Mauer]].
+:[[1990]] [[Wiedervereinigung]] Deutschlands und der beiden Stadthälften [[Ost-Berlin|Ost-]] und [[West-Berlin]], Berlin wird Hauptstadt im vereinigten Deutschland.
+:seit [[1997]]: Der [[Berliner Bankenskandal]] um die "[[Bankgesellschaft Berlin]]" [http://www.bankgesellschaft.de] bereitet der Stadt und dem Land Berlin enorme finanzielle und fiskalische Probleme, die die Handlungsfähigkeit der Stadtverwaltung einschränken. Berlin klagt derzeit beim [[Bundesverfassungsgericht]] wegen einer "extremen Haushaltsnotlage", um eine Bundesergänzungszuweisung von 35 Milliarden Euro zum Schuldenabbau zu erhalten.
+
+=== Bevölkerung ===
+
+Im Jahr [[2003]] hatte Berlin knapp 3,4 Millionen Einwohner. Die [[Türkei|türkische]] und [[Kurden|kurdische]] Gemeinde Berlins umfasste 200.000 Köpfe. Außerdem wohnten in Berlin 180.000 [[Polen]] (9% aller Polen, die in [[Deutschland]] wohnten).
+Am letzten Stichtag, den 31.12.2000, lebten rund 430.000 Einwohner mit ausländischen Pass aus 185 Staaten in Berlin.
+<!--http://www.statistik-berlin.de/pms2000/sg03/2001/01-03-06.html-->
+
+Das durchschnittliche Lebensalter der Berliner Bevölkerung betrug 41,7 Jahre im Jahre 2004. Das entspricht einer Erhöhung von 2,5 Jahren in den letzten zwölf Jahren.
+
+== Politik ==
+
+[[Bild:Wahl_zum_Berliner_Abgeordnetenhaus_2001.png|thumb|Ergebnisse der Wahl zum Abgeordnetenhaus 2001]]
+
+Berlin ist seit der Wiedervereinigung der ehemals getrennten beiden deutschen Staaten am 3. Oktober 1990 ein vollwertiges Bundesland und zugleich eine kreisfreie Stadt, die in nunmehr 12 [[Stadtbezirk|Bezirk]]e untergliedert ist. Daneben ist Berlin die [[Hauptstadt]] der Bundesrepublik [[Deutschland]] und damit Sitz der [[Bundesregierung]], des [[Bundestag]]es und des [[Bundesrat]]es.
+
+=== Das Land Berlin ===
+
+Die [[Landesregierung]], also [[exekutive]] Ebene wird durch den [[Senat von Berlin]], bestehend aus dem Regierenden Bürgermeister (derzeit [[Klaus Wowereit]], SPD) und bis zu acht [[Senator]]en ausgeübt. Der [[Regierende Bürgermeister]] ist dabei zugleich Repräsentant des Landes und der Stadt.
+
+Die [[Legislative|gesetzgebende Gewalt]] ist das [[Abgeordnetenhaus von Berlin]] in welchem derzeit eine [[Koalition]] aus [[Sozialdemokratische Partei Deutschlands|Sozialdemokratischer Partei Deutschlands]] ([[SPD]]) und der [[Partei des demokratischen Sozialismus]] ([[PDS]]) die Mehrheit der [[Abgeordneter|Abgeordneten]] stellt. Oberstes Landesgesetz ist die [[Verfassung von Berlin]].
+
+=== Die Bezirke ===
+
+Die [[Bezirksverwaltung in Berlin|Bezirke]] stellen keine eigenständigen [[Gemeinde]]n dar und sind somit stark vom Senat und auf Verwaltungsebene von den [[Senatsverwaltung]]en abhängig, die die [[Fachaufsicht]] ausüben.
+
+Dennoch gibt es in jedem Berliner Bezirk ein eigenes ''Bezirksparlament'', die ''Bezirksverordnetenversammlung (BVV)'', welches das ''politische Bezirksamt'', bestehend aus Bezirksbürgermeister und fünf [[Stadtrat|Stadträten]] wählt. Bürgermeister und Stadträte werden zwar politisch gewählt, haben aber den Status eines ''Abteilungsleiters'' und sind verbeamtet, so genannte ''[[Beamter|Wahlbeamte]]''.
+
+Die Bürgermeister der Bezirke bilden unter Vorsitz des Regierenden Bürgermeisters das Gremium des ''[[Rat der Bürgermeister|Rates der Bürgermeister]]'', welcher den Senat berät.
+
+=== Länderfusion Berlin-Brandenburg ===
+
+Im Jahre [[1996]] scheiterte eine [[Volksabstimmung]] zur Länderfusion der Bundesländer Berlin und Brandenburg, vor allem durch die politische Dominanz Berlins, der bürgerfernen Diskussion um die Fusion und aufgrund der Finanzprobleme des Landes Berlin. Trotzdem wurde in den letzten Jahren die Zusammenarbeit der beiden Bundesländer intensiviert und viele Einrichtungen zusammengelegt. Teilweise wird eine neue Volksabstimmung über die Fusion für die nächsten Jahrzehnte diskutiert.
+
+Vor der [[Wiedervereinigung]] Deutschlands hatte der Begriff ''[[Berlin-Politik]]'' sogar einen weltpolitischen Inhalt in der Ost-West-Auseinandersetzung.
+
+''Siehe auch:'' [[West-Berlin]], [[Ost-Berlin]]
+
+=== Städtepartnerschaften ===
+
+Berlin unterhält mit folgenden Städten eine [[Städtepartnerschaft]]. Die Partnerschaften wurden durch einen Vertrag des [[Senat von Berlin|Senats]] mit der jeweiligen Stadt vereinbart. Zwischen [[1968]] und [[1986]] wurden nur auf Bezirksebene Partnerschaften abgeschlossen. Diese sind hier nicht wiedergegeben.
+
+*[[Los Angeles]], Bundesstaat [[Kalifornien]] [[USA]], seit 1967
+*[[Paris]], [[Frankreich]], seit 1987
+*[[Madrid]], [[Spanien]], seit 1988 ¹
+*[[Istanbul]], [[Türkei]], seit 1989
+*[[Moskau]], [[Russische Föderation]], seit 1990 ¹
+*[[Warschau]], [[Polen]], seit 1991 ¹
+*[[Budapest]], [[Ungarn]], seit 1991 ¹
+*[[Brüssel]], [[Belgien]], seit 1992
+*[[Jakarta]], [[Indonesien]], seit 1993
+*[[Taschkent]], [[Usbekistan]], seit 1993
+*[[Mexiko-Stadt]], [[Mexiko]], seit 1993
+*[[Peking]], [[Volksrepublik China]], seit 1994 ¹
+*[[Tokio]], [[Japan]], seit 1994
+*[[Buenos Aires]], [[Argentinien]], seit 1994
+*[[Prag]], [[Tschechien]], seit 1995 ¹
+*[[Windhuk]], [[Namibia]], seit 2000
+
+¹ Diese Städte hatten vor 1990 auch mit Ost-Berlin einen Partnerschaftsvertrag, der vom Magistrat der Stadt abgeschlossen wurde. Nach der Wiedervereinigung wurden diese Verträge jedoch außer Kraft gesetzt und später vom Senat Gesamt-Berlins überarbeitet.
+
+
+=== weiterführende Links ===
+
+*[[Verfassung von Berlin]] [http://www.parlament-berlin.de/parlamentb.nsf/SystemHTML/FramesetLR?OpenDocument&_0H15_1H15U10_2../Dokumente/Verfassung?OpenDocument_3]
+*[[Abgeordnetenhaus von Berlin]] [http://www.abgeordnetenhaus-berlin.de]
+*[[Regierender Bürgermeister von Berlin]] [http://www.berlin.de/RBmSKzl]
+*[[Senat von Berlin]] und [[Bezirksverwaltung in Berlin]] unter der offiziellen Webseite von Berlin [http://www.berlin.de]
+*[[Wahlen in Berlin|Wahlergebnisse]] beim Statistischen Landesamt [http://www.stala.verwalt-berlin.de]
+
+== Kultur und Sehenswürdigkeiten ==
+[[Bild:Brandenburger Tor 2003.jpg|thumb|Das Brandenburger Tor]]
+<!-- Hier sollte eine Kurzbeschreibung der wichtigsten Sehenswürdigkeiten und Kulturdenkmäler hin -->
+
+'''Siehe:''' [[Sehenswürdigkeiten in Berlin]]
+
+=== Kunst und Kultur ===
+
+[[Galerien in Berlin]], [[Museen in Berlin]], [[Kirchen in Berlin]], [[Historischer Hafen Berlin]], [[Staatsoper Unter den Linden]], [[Deutsche Oper Berlin]], [[Komische Oper Berlin]], [[Philharmonie Berlin]], [[Internationale Filmfestspiele Berlin]], [[Jüdisches Museum Berlin]]
+
+=== Sport ===
+
+'''Sport:''' [[Hertha BSC Berlin]], [[Berlin-Marathon]], [[Eisbären Berlin]], Eishockey, [[Moderner Fünfkampf]], [[Wasserfreunde Spandau]], Wasserball, [[Alba Berlin|Alba Berlin (Basketball)]], [[Berlin Thunder]] ([[American Football]])
+
+== Wirtschaft ==
+[[Bild:Bundeskanzleramt.JPG|thumb|left|Das Marie-Elisabeth-Lüders-Haus]]
+Das [[Bruttoinlandsprodukt]] des Landes Berlin betrug im Jahr 2001 75,8 Mrd. €. Zum Vergleich: Hamburg erzielte ein Bruttoinlandsprodukt von 73,7 Mrd. €, im Land Brandenburg waren es 42,3 Mrd. € (IHK-Bericht 2000/2001).
+
+In Berlin gab es 2001 insgesamt 138.688 der Industrie- und Handelskammer zugehörige Betriebe. Davon waren 48.289 im Handelsregister eingetragen und 90.399 so genannte Kleingewerbetreibende (IHK-Bericht 2000/2001).
+
+=== Berliner Wirtschaft 2001 ===
+
+Die wirtschaftliche Entwicklung Berlins war im Jahre [[2001]] meist von negativen [[Trend]]s bestimmt. Das Bruttoinlandsprodukt sank im Vergleich zum Vorjahr um ein halbes Prozent, die Zahl der Erwerbstätigen sank um 5000 Beschäftigte,
+die Arbeitslosenquote lag im November 2001 bei 16 Prozent. Berlin befand sich damit im Geleitzug aller nordostdeutschen Bundesländer, die alle eine
+Arbeitslosenquote zwischen 14 Prozent (Thüringen) und 18 Prozent (Sachsen-Anhalt) aufwiesen und damit weit über dem Bundesdurchschnitt von 9,2 Prozent lagen.
+[[Bild:Deutscher Dom.JPG|thumb|Der Deutsche Dom bei Nacht]]
+Positive Impulse gingen allein vom Dienstleistungsbereich, besonders der unternehmensnahen Dienstleistungen, aus.
+
+Im verarbeitenden Gewerbe verzeichnete der Fahrzeugbau, die chemische
+Industrie und die Elektrotechnik zum Teil kräftige Umsatzsteigerungen.
+
+Einzelhandel und Gastgewerbe litten unter mangelnder privater Nachfrage.
+
+In einer schweren Krise steckt seit Jahren die Berliner Bauwirtschaft, die offensichtlich vom Bauboom der Jahre nach 1990 nicht nachhaltig profitieren konnte.
+
+Positiv entwickelte sich die Neugründung von Unternehmen in Berlin und die Zahl der Gewerbeanmeldungen. Der Saldo von Gewerbean- und abmeldungen ist seit Jahren positiv, hatte [[1990]] einen vereinigungsbedingten Höchststand von über 26.000, und liegt nach einem Tief im Jahre [[1999]] aktuell wieder bei 4.000 mehr Anmeldungen als Abmeldungen.
+
+Die Terroranschläge des [[11. September]] [[2001]] in den USA hatten sofort auch negative Auswirkungen in Berlin, vor allem in den Touristikbereichen, die eine sehr große wirtschaftliche Rolle in der Bundeshauptstadt spielen. So ging das Passagieraufkommen der drei Berliner Flughäfen im Vergleich zum Jahr [[2000]] von 13,3 Millionen auf 12,6 Millionen Passagiere zurück.
+
+Große Erwartungen werden in Berlin in die Innovations- und Technologiezentren gesetzt. Der größte europäische Technologiestandort auf dem Gelände der ehemaligen [[Akademie der Wissenschaften der DDR]], bekannt unter der Kurzform [[WISTA]] in [[Adlershof]] mit den Schwerpunkten Umwelttechnologie,Informations- und Kommunikationstechnologie und Optoelektronik sowie der Biotechnologiestandort [[Berlin-Buch]] nehmen eine ausgesprochen positive Entwicklung und sind in internationale Netzwerke eingebunden.
+
+Berlin ist daher entschlossen, den Standortfaktor Wissenschaft und Forschung zu nutzen und auszubauen. Entschiedene Fördermaßnahmen sind aber in Folge des [[Berliner Bankenskandal|Berliner Bankenskandals]] und der sich hieraus ergebenden Haushaltsnöte unmöglich.
+
+''Siehe auch:'' [[Berlin - Wirtschaft]]
+
+=== Medien ===
+
+==== Rundfunk- und Fernseh-Sender ====
+
+*[[Rundfunk Berlin-Brandenburg]] (RBB), entstanden 2003 aus dem [[Sender Freies Berlin]] (SFB) und dem [[Ostdeutscher Rundfunk Brandenburg|Ostdeutschen Rundfunk Brandenburg]] (ORB)
+*[[Sat1]], hat seine Deutschlandzentrale in der Oberwallstraße.
+*[[N24]]
+*[[Deutsche Welle ]] TV
+*[[XXP]]
+*[[MTV]] - zog von Hamburg und München nach Berlin: in die Mediacity Oberspree im Bezirk [[Friedrichshain-Kreuzberg]].
+*[[n-tv]] - Umzug in die Sendezentrale des Muttersenders RTL nach Köln zum 01.09.2004
+*TV Berlin (tvb), privates Ballungsraumfernsehen
+*Fernsehen aus Berlin ([[FAB]]), privates Ballungsraumfernsehen, Sendestart: 1. Februar 1991
+*[[Offener Kanal Berlin]]
+*[[DeutschlandRadio Berlin]], entstanden aus [[RIAS]] Berlin und [[DS Kultur]]
+*[[Privatradio]]s (Auswahl): Berliner Rundfunk, JAM FM, r.s.2, Metropol FM, Radio Paradiso, KISS FM, Klassik Radio, Jazz Radio, [[Nouvelle Radio Jeunesse|Energy]] 103,4, Nova Radio/Joy FM/Kaufradio/RadiJojo, 104.6 RTL, Radio Power 4, Spreeradio, RFI, BB Radio, Hundert,6
+
+==== Druckmedien ====
+[[Bild:Hotel Adlon.JPG|thumb|left|Das Hotel Adlon am Pariser Platz]]
+* [[Die Welt]], [[Der Tagesspiegel]], [[die tageszeitung]], [[Berliner Zeitung]], [[Berliner Morgenpost]], [[B.Z.]], Berliner Kurier
+
+'''Stadtmagazine'''
+
+* [[Tip (Magazin)|Tip]], [[Zitty]]
+
+==== Sendeanlagen von Rundfunksendern ====
+[[Bild:Fernsehturm.JPG|thumb|Der Berliner Fernsehturm]]
+
+Im Stadtgebiet von Berlin befinden sich zahlreiche Rundfunksendeanlagen für alle Wellenbereiche (außer Langwelle). Neben dem 368 Meter hohen [[Berliner Fernsehturm]] am Alexanderplatz, von dem aus die meisten Programme abgestrahlt werden, sind dies:
+* der 212 Meter hohe [[Fernmeldeturm Berlin-Schäferberg]] (Sendeanlagen für [[UKW]] und TV)
+* der UKW- und TV-Sendemast des RBB am [[Scholzplatz]] ([[1963]] errichteter 230 Meter hoher, abgespannter Rohrmast)
+* der MW-Sender des RBB in der [[Stallupöner Allee]] (ein [[1987]] errichter, gegen Erde isolierter 130 Meter hoher Stahlfachwerkmast. Bis [[1998]] existierte noch ein zweiter ebenfalls gegen Erde isolierter Sendemast, der [[1948]] errichtet wurde)
+* die Sendeanlage für [[Mittelwelle]], [[Kurzwelle]] und UKW von Deutschlandradio (früher [[RIAS]]) in [[Berlin-Britz]]. Als Sendeantennen für UKW und MW sind zwei gegen Erde isolierte, abgespannte Stahlfachwerkmasten mit Höhen von 160 beziehungsweise 144 Metern vorhanden, die 1948 errichtet wurden. Für die Kurzwellensender existieren auf dem Areal der Anlage zwei Dipolantennen
+* ein 180 Meter hoher, abgespannter Stahlfachwerkmast beim [[Berliner Olympiastadion]].
+
+Der [[Berliner Funkturm]] dient seit [[1973]] nicht mehr zur Verbreitung von Rundfunkprogrammen, sondern nur noch als Relaisstation für Funkdienste des nichtöffentlichen Landfunkdienstes und des Amateurfunks.
+Bis [[2002]] existierte in [[Berlin-Köpenick]] eine Sendeanlage für UKW und MW mit einem 248 Meter hohen gegen Erde isolierten, abgespannten Stahlrohrmast. Diese Anlage ist inzwischen abgebrochen worden. Der Betrieb der dort befindlichen UKW-Sender wurde zum Berliner Fernsehturm, der der MW-Sender nach [[Zehlendorf bei Oranienburg|Zehlendorf]] bei Oranienburg verlegt.
+
+Eine weitere bemerkenswerte funktechnische Anlage im Stadtgebiet ist die Richtfunkstelle [[Berlin-Frohnau]], die über einen 358,70 Meter hohen abgespannten Stahlfachwerkmast und einem 117 Meter hohen Stahlgitterturm verfügt.
+
+Von 1933 bis 1948 war in Berlin-Tegel Standort des Rundfunksenders für Berlin. Die Gebäude stehen noch und werden von der Deutschen Telekom AG zu Meßzwecken benutzt, während die Antennenträger 1948 demontiert wurden.
+In Berlin-Adlershof befand sich vor dem 2. Weltkrieg die zentrale Funkstelle der Polizei mit 2 120 Meter hohen, selbststrahlenden Sendemasten. Zu Beginn der 50er Jahre wurde in Berlin-Adlershf ein Radioteleskop mit 36 Meter Durchmesser errichtet.
+
+Auf dem [[Teufelsberg]] befindet sich eine große Radaranlage, die einst den Alliierten zur Überwachung des Luftraums diente und heute der DFS zur Sicherung des zivilen Flugverkehrs dient.
+
+== Verkehr ==
+
+=== Öffentlicher Verkehr ===
+
+[[Bild:Potsdamer Platz.JPG|thumb|Potsdamer Platz]]
+
+Dem innerstädtischen Verkehr dienen die [[S-Bahn Berlin|S-Bahn]] – betrieben von der S-Bahn Berlin GmbH – sowie die [[U-Bahn Berlin|U-Bahn]], [[Straßenbahn]]en und [[Omnibus|Busse]] – betrieben von der [[Berliner Verkehrsbetriebe|BVG]].
+
+In Ost-West-Richtung wird die Innenstadt von der als Hochbahn angelegten [[Berliner Stadtbahn|Stadtbahn]] durchquert. Sie verbindet den [[Berlin Ostbahnhof|Ostbahnhof]] mit dem [[Bahnhof Berlin Charlottenburg|Bahnhof Charlottenburg]], über [[Alexanderplatz]], [[Bahnhof Berlin-Friedrichstraße|Friedrichstraße]] und [[Bahnhof Berlin Zoologischer Garten|Zoologischer Garten]]. Unterstützt wird die Ost-West-Verbindung durch die U-Bahnlinie 5 (U5), die teilweise im Ostteil Berlins oberirdisch verläuft. Zurzeit wird die Ost-West-Trasse durch eine teilweise unterirdische Nord-Süd-Trasse ergänzt (Teil des Pilzkonzepts), die am Lehrter Bahnhof (offizieller Name heute ''Berlin Hauptbahnhof-Lehrter Bahnhof'') die Stadtbahn kreuzt.
+
+Vervollständigt wird der S-Bahn-Verkehr durch die [[Berliner Ringbahn|Ringbahn]], auf der man in 63 Minuten einmal um die Innenstadt herum fahren kann.
+
+Zum Eisenbahnknotenpunkt Berlin gehört auch der bei [[Potsdam]] gelegene [[Rangierbahnhof]] Seddin.
+
+Für den Regionalverkehr verkehren Regionalbahnen und -expresslinien der [[Deutsche Bahn|Deutschen Bahn]] sowie zwei [[InterConneX]]-Linien.
+
+''Siehe auch:'' [[Geschichte des ÖPNV Berlins]], [[Liste Berliner Bahnhöfe]], [[Liste Berliner U-Bahnhöfe]]
+
+===Autobahnen===
+Die Innenstadt wird von Westen her von einem [[Autobahn]]-Halbkreis ([[Bundesautobahn 100|A 100]] – Berliner Stadtring) umgeben, der langfristig zu einem Ring vervollständigt werden soll. Rund um Berlin verläuft die Autobahn [[Bundesautobahn 10|A&nbsp;10]] (Berliner Ring).
+
+Von der A&nbsp;100 gibt es innerhalb des Stadtgebiets folgende Autobahnen Richtung Berliner Ring:
+* [[Bundesautobahn 111|A 111]] in Richtung Nordwesten (Richtung [[Hamburg]] und [[Rostock]])
+* [[Bundesautobahn 113|A 113]] in Richtung Südosten (Richtung [[Dresden]] und [[Cottbus]]). Diese Autobahn beginnt derzeit erst am Dreieck Treptow im äußersten Südosten der Stadt. Bis [[2007]] soll der im Bau befindliche Anschluss von der A&nbsp;100 (beginnend am Dreieck Neukölln) bis zur Berliner Stadtgrenze fertig gestellt sein. Am [[14. Juli]] [[2004]] wurde dazu das erste Teilstück vom Dreieck Neukölln bis zur Anschlussstelle Späthstraße für den Verkehr freigegeben.
+* [[Bundesautobahn 115|A 115]] in Richtung Südwesten (Richtung [[Hannover]] und [[Leipzig]]), teilweise auf der [[AVUS]].
+
+Zusätzlich dazu gibt es im Norden der Stadt noch die [[Bundesautobahn 114|A 114]] von der Prenzlauer Promenade im Bezirk [[Pankow]] zur A&nbsp;10. Die nur wenige Kilometer lange ehemalige [[Bundesautobahn 104|A 104]], die im Südwesten der Stadt [[Steglitz]] mit der A&nbsp;100 verbindet, wurde inzwischen zur [[Autostraße]] herabgestuft.
+
+===Flughäfen===
+Berlin besitzt drei Flughäfen in [[Flughafen Berlin-Tegel|Tegel]], [[Flughafen Berlin-Tempelhof|Tempelhof]] und [[Flughafen Berlin-Schönefeld|Schönefeld]]. Langfristig ist geplant den Flughafen [[Flughafen Berlin-Schönefeld|Schönefeld]] zu vergrößern um die im Innenstadtbereich liegenden Flughäfen [[Flughafen Berlin-Tegel|Tegel]] und [[Flughafen Berlin-Tempelhof|Tempelhof]] zu entlasten beziehungsweise zu schließen. Das Großprojekt Flughafen [[Flughafen Berlin-Schönefeld|Schönefeld]] trifft jedoch bei vielen Berlinern und Brandenburgern auf großen Widerstand.
+
+== Wissenschaft und Forschung ==
+
+=== Hochschulen in Berlin ===
+In Berlin studieren an insgesamt 17 Universitäten und Hochschulen rund 150.000 Studentinnen und Studenten:
+*[[Freie Universität Berlin]] (FU) http://www.fu-berlin.de
+*[[Humboldt-Universität zu Berlin]] (HU) http://www.hu-berlin.de
+*[[Charité]] Berlin (Gemeinsame Medizinische Fakultät von HU und FU) http://www.charite.de
+*[[Technische Universität Berlin]] (TU) http://www.tu-berlin.de
+*[[Fachhochschule für Technik und Wirtschaft]] (FHTW) http://www.fhtw-berlin.de
+*Fachhochschule für Wirtschaft (FHW) http://www.fhw-berlin.de
+*Fachhochschule für Verwaltung und Rechtspflege http://www.fhvr-berlin.de
+*[[Universität der Künste Berlin]] (UdK, früher: Hochschule der Künste - HdK) http://www.udk-berlin.de
+*Hochschule für Musik "Hanns Eisler" (HfM) http://www.hfm-berlin.de
+*Hochschule für Schauspielkunst "Ernst Busch" http://www.hfs-berlin.de/
+*Kunsthochschule Berlin-Weißensee Hochschule für Gestaltung (KHB) http://www.kh-berlin.de
+*[[Technische Fachhochschule Berlin]] (TFH) http://www.tfh-berlin.de
+*Alice-Salomon Fachhochschule für Sozialarbeit und Sozialpädagogik (ASFH) http://www.asfh-berlin.de
+*Katholische Hochschule für Sozialwesen Berlin (KHSB) http://www.khsb-berlin.de
+*Evangelische Fachhochschule Berlin (EFB) http://www.evfh-berlin.de
+*Touro College Berlin http://www.touroberlin.de/
+*Berufsakademie Berlin http://www.ba-berlin.de/
+
+=== Forschungsinstitute in Berlin ===
+*Max-Planck-Institut für Bildungsforschung http://www.mpib-berlin.mpg.de
+*Max-Planck-Institut für Infektionsbiologie http://www.mpiib-berlin.mpg.de
+*Max-Planck-Institut für molekulare Genetik http://www.molgen.mpg.de
+*Max-Planck-Institut für Wissenschaftsgeschichte http://www.mpiwg-berlin.mpg.de
+*Fraunhofer-Institut für Nachrichtentechnik - Heinrich-Hertz-Institut http://www.hhi.fraunhofer.de
+*Fraunhofer-Institut für Offene Kommunikationssysteme (FOKUS)
+*Fraunhofer-Institut für Produktionsanlagen und Konstruktionstechnik (IPK)
+*Fraunhofer-Institut für [[Rechnerarchitektur]] und [[Softwaretechnik]] (FIRST)
+*Fraunhofer-Institut für Software- und Systemtechnik (ISST)
+*Fraunhofer-Institut für Zuverlässigkeit und [[Mikrointegration]] (IZM)
+*Berliner Elektronenspeicherring-Gesellschaft m.b.H. BESSY http://www.bessy.de
+*Ecologic gGmbH http://www.ecologic.de
+*Institut für Ökologische Wirtschaftsforschung gGmbH (IÖW) http://www.ioew.de
+*Fritz-Haber-Institut der Max-Planck-Gesellschaft http://www.fhi-berlin.mpg.de
+*Hahn-Meitner-Institut [http://www.hmi.de/ hmi.de]
+*Institut für Planetenforschung des [[Deutsches Zentrum für Luft- und Raumfahrt|DLR]] http://solarsystem.dlr.de/
+*Physikalisch-Technische Bundesanstalt [http://www.ptb.de/ PTB.de]
+*Umweltbundesamt (UBA) http://www.umweltbundesamt.de
+<!-- == Städtepartnerschaften == -->
+
+=== Wichtige Berliner Persönlichkeiten===
+
+*Politiker
+**[[Willy Brandt]], ([[Sozialdemokratische Partei Deutschlands|SPD]])
+**D. Dr. [[Hermann Ehlers]], ([[CDU]]), [[Bundestagspräsident]]
+**Dr. [[Victor-Emanuel Preusker]] ([[1913]]-[[1991]]), ([[FDP (Deutschland)|FDP]], später [[CDU]]), [[MdB]], Bundesminister für Wohnungsbau
+**[[Ernst Reuter]], ([[Sozialdemokratische Partei Deutschlands|SPD]])
+
+*andere Persönlichkeiten
+**[[Alexander Beer]], jüdischer Architekt
+**[[Adolf Damaschke]], Bodenreformer
+**[[Hans Dominik]], Schriftsteller (technische Zukunftsliteratur), Journalist und Ingenieur
+**[[Gottfried Wilhelm Lehmann]], Baptistenpastor
+**[[Manfred Maurenbrecher]], Liedermacher und Autor
+**[[Werner-Viktor Toeffling]], Maler und Bühnenbildner
+**[[1932]], [[Charles Wilp]], Werbemacher und Künstler (Afri Cola Rausch, VW Käfer ''Und läuft ... und läuft ... und läuft'')
+
+''Siehe auch:'' [[Liste der Ehrenbürger von Berlin]]
+
+== Siehe auch ==
+
+* [[Geschichte Berlins]], [[Wirtschaft Berlins]], [[Berlin-Literatur]]
+* [[Berlinerisch]], [[Berliner]]
+* [[Liste der deutschen Hauptstädte]] in der Geschichte
+* [[Metropolregion Berlin/Brandenburg]].
+
+== Literatur ==
+
+* Presse- und Informationsamt des Landes Berlin (Hg.), ''Berlin Handbuch - Das Lexikon der Bundeshauptstadt''; Berlin 1992, (ISBN 3-927551-27-9)
+*Hosfeld, Rolf/Teicke, Friehelm/Vogt, Tobias/Wörtmann, Rainer: ''Berlin Kultur(ver)führer 2004'', Helmut Metz : Hamburg, 3. Aufl. 2004, 255 S., (ISBN 3-9807381-7-5)
+* Philipp Oswalt, ''Berlin - Stadt ohne Form''; München, London, New York 2000 (ISBN 3-7913-24403)
+* Bernhard van Treeck, ''Street Art Berlin''; Berlin 1999 (ISBN 3-89602-191-5)
+
+==Weblinks==
+* [http://www.berlin.de/ Offizielle Website der Hauptstadt Deutschlands]
+* [http://www.berlin-ehrungen.de/indexthemen.htm Daten zu Berlin]
+
+[[Kategorie:Ort in Deutschland]]
+[[Kategorie:Ort in Berlin| ]]
+[[Kategorie:Hauptstadt]]
+[[Kategorie:Bundesland (Deutschland)]]
+[[Kategorie:Berlin| ]]
+
+[[ar:برلين]]
+[[bg:Берлин]]
+[[cs:Berlín]]
+[[cy:Berlin]]
+[[da:Berlin (hovedstad)]]
+[[en:Berlin]]
+[[eo:Berlino]]
+[[es:Berlín]]
+[[et:Berliin]]
+[[fi:Berliini]]
+[[fr:Berlin]]
+[[he:ברלין]]
+[[id:Berlin]]
+[[it:Berlino]]
+[[ja:ベルリン]]
+[[la:Berolinum]]
+[[lt:Berlynas]]
+[[nds:Berlin]]
+[[nl:Berlijn]]
+[[no:Berlin]]
+[[pl:Berlin]]
+[[pt:Berlim]]
+[[ro:Berlin]]
+[[ru:Берлин]]
+[[simple:Berlin]]
+[[sk:Berlín]]
+[[sl:Berlin]]
+[[sv:Berlin]]
+[[tokipona:ma tomo Pelin]]
+[[uk:Берлін]]
+[[zh:柏林]] \ No newline at end of file
diff --git a/vendor/wikimedia/utfnormal/scripts/testdata/bulgakov.txt b/vendor/wikimedia/utfnormal/scripts/testdata/bulgakov.txt
new file mode 100644
index 00000000..6eaa8534
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/testdata/bulgakov.txt
@@ -0,0 +1,40 @@
+'''Сергей Николаевич Булгаков''' родился в [[1873]] г. в небольшом городке Ливны Орловской губернии в семье священника. Учился в духовном училище и в Орловской духовной семинарии, затем в Елецкой гимназии. В [[1894]] г. закончил юридический факультет Московского университета.
+
+Еще во время учебы в семинарии Булгаков пережил религиозный кризис и увлекся марксизмом. В [[университет|университете]] он серьезно занимался изучением политической экономии.
+
+Углубленно занимаясь [[марксизм|марксизмом]], Булгаков со временем понял несостоятельность этого учения. Под воздействием чтения русских религиозных мыслителей ([[Толстой, Лев Николаевич|Льва Толстого]], [[Достоевский, Фёдор Михайлович|Фёдора Достоевского]], [[Соловьёв, Владимир Сергеевич|Владимира Соловьева]] и др.), бесед и споров с Толстым он снова обретает религиозную веру (см. Серней Булгаков "От марксизма к идеализму". Москва, [[1903]]). Подобная эволюция была характерна для русской интеллигенции той поры, и вскоре Булгаков выдвигается в ряд ее признанных духовных лидеров. Он стал одним из основных участников сборника "Проблемы идеализма" ([[1902]]), где впервые объединились ведущие лица нарождавшегося [[религия|религиозно]]-[[философия|философского]] движения; название сборника его статей "От марксизма к идеализму" (1903) сделалось крылатым словом, выразившим духовный смысл исторического момента.
+
+Последующие годы период наибольшей общественной, и публицистической, активности [[философ|философа]]. Он участвует во множестве начинаний, знаменующих собой религиозно-философского возрождение в журнале "Новый путь" и "Вопросы жизни", сборнике "Вопросы религии", "О Владимире Соловьеве", "О религии Льва Толстого", "Вехи", в работе "Религия философского общества памяти Владимира Соловьева" и книгоиздательства "Путь", где в [[1911]]-[[1917|17]] гг. выходили в свет важнейшие произведения русской религиозной мысли. В [[1906]] г. он был также избран депутатом Второй Государственной Думы (как беспартийный "христианский социалист"). В творчестве его в этот период совершается переход от лекций и статей на темы религии и [[культура|культуры]] (важнейшие из них были им собраны в двухтомник "Два града", [[1911]]) к оригинальным философским разработкам. В монографиях "Философия хозяйства" (1912) и главным образом "Свет Невечерний" (1917) он намечает основы собственного учения, идущего в русле софиологии Владимира Соловьева и Флоренского, однако вобравшего и заметное влияние позднего Шеллинга, а также ряд собственных идей, питаемых интуициями [[православие|православной]] религиозности. Процесс постепенного возврата к церковно-православному миросозерцанию завершается уже в революционные годы принятием священства ([[1918]]). С этим завершением Булгаков сразу же начинает играть видную роль также и в церковных кругах, активно участвуя в работе Всероссйского Поместного Собора Православной Церкви (1917-18) и близко сотрудничая с патриархом Тихоном. Восприняв безусловно отрицательно [[Октябрьская революция 1917|Октябрьский переворот]], отец Сергий быстро откликнулся на него диалогами "На пиру богов", написанными в стиле и духе "Трех разговоров" Владимира Соловьева; диалоги вошли в коллективном сборнике "Из глубины" (1918; 2-е изд. М., 1991). В годы [[Гражданская война 1917-22|гражданской войны]] отец Сергий находился в Крыму и, будучи оторван как от иерейского служения, так и от общественно-публицистической деятельности, интенсивно работал в философии. В написанных тогда сочинениях "Философия имени" (1920, изд. 1953) и "Трагедия философии" (1920, изд. в нем. пер. 1928) он подверг пересмотру свой взгляд на соотношение философии и догматики [[христианство|христианства]], придя к выводу о том, что христианское умозрение способно выразиться без искажений исключительно в форме догматического богословия. Последнее и стало с тех пор основной сферою его творчества.
+
+В [[1922]] г. отец Сергий был включен в составленные ГПУ по инициативе [[Ленин, Владимир Ильич|В.И. Ленина]] списки деятелей науки и культуры, подлежащих высылке за рубеж. 30 декабря 1922 г. он отправляется из Крыма в изгнание и после недолгого пребывания в [[Стамбул|Константинополе]] в мае [[1923]] г. занимает должность профессора церковного права и богословия на юридическом факультете Русского Научного Института в [[Прага|Праге]]. Вскоре при его ближайшем участии возникает и успешно осуществляется проект создания в [[Париж|Париже]] Православного Богословского Института. С его открытия в [[1925]] г. и до своей кончины отец Сергий был его бессменным главой, а также профессором кафедры догмат, богословия. Под его руководством Сергиевское Подворье как стали называть комплекс институтских строений с храмом во имя преподобного [[Сергий Радонежский|Сергия Радонежского]] выросло в крупнейший центр православной духовности и богословской науки в зарубежье. Пастырская, профессорская и руководящая работа в Институте ядро всей деятельности отца Сергия в последнее двадцатилетие его жизни.
+
+Эта деятельность была чрезвычайно многогранна. Помимо дел, связанных с Институтом, и помимо богословского творчества, отец Сергий уделял большое внимание еще по меньшей мере двум сферам: дух. руководству русской молодежью и участию в экуменическом движении. Центральным руслом религиозной активности русской молодежи за рубежом стало Русское Студенцеское Христианское Движение. Отец Сергий был одним из главных его отцов-основателей. Он участвовал в его зарождении, в первых съездах РСХД в Пшерове ([[Чехословакия]]) и Аржероне ([[Франция]]) и продолжал постоянно его курировать, оставаясь для членов Движения незаменимым наставником и авторитетом. В работу экуменич. движения о. Сергий включился в [[1927]] г. на Всемирной христианской конференции "Вера и церковное устройство" в Лозанне. До конца 30-х гг. он принял участие во многих экуменических начинаниях, став одним из влиятельных деятелей и идеологов движения; в [[1934]] г. он совершил большую поездку по [[Соединённые Штаты Америки|США]]. Наиболее перспективным направлением в экуменической сфере оказалось сотрудничество с англиканской церковью. Объективные возможности для сближения между православием и англиканством указывались и "были признаны со времен Хомякова; трудами отца Сергия и его сподвижников (оотца Георгия Флоровcкoгo, Н.М. Зернова, Г.П. Федотова и других) они начали воплощаться в жизнь. В конце 1927 начале [[1928]] г. проходит англо-русский религиозный съезд, результатом которого стало учреждение двухстороннего Содружества святого Албания (древнеанглийский святой мученик) и преподобного Сергия Радонежского. Это Содружество продолжает свою деятельность поныне.
+
+В [[1939]] г. у отца Сергия был обнаружен рак горла. Он перенес опасные операции, побывал на пороге смерти и в значительной степени утратил способность речи. Начавшаяся [[Вторая мировая война]] ограничила еще более сферу его трудов. Однако до последних дней жизни, в тяжелых условиях оккупированного Парижа, он не прекращал служить литургию и читать лекции (что стоило ему огромных усилий), а также работать над новыми сочинениями. Его творчество обладает редкой цельностью: всем его главным темам он сумел подвести итог и дать отчетливое завершение. Как и в каноне Священного Писания, его последняя кн. он закончил ее совсем незадолго до смерти "Апокалипсис Иоанна".
+
+Творчество отца Сергия начиналось с публицистики, статей на экономические, культурно-общественные, и религиозно-филоские темы. Помимо начального этапа, публицистика выходила на первый план в критические моменты жизни [[Россия|России]]: [[революция 1905-07]] гг., начало первой мировой войны, 1917 г. Целый ряд существенных тем булгаковской мысли остался развит почти исключительно в данной форме: религия и культура, христианство, [[политика]] и [[социализм]], задачи общественности, путь русской интеллигенции, проблемы церковной жизни, проблемы искусства... Булгаков не просто участник знаменитых "Вех" (1909, статья "Героизм и подвижничество"), но и один из главных выразителей "веховства" как идейного движения, призвавшего интеллигенцию к отрезвлению, отходу от стадной морали, утопизма, оголтелого революционерства в пользу работы духовного осмысления и конструктивной социальной позиции. В этот же период он разрабатывает идеи социалистического христианства, в широком спектре, включающем анализ христианского отношения к экономике и политике (с апологией социализма, постепенно шедшей на убыль), критику марксизма, но равно и буржазно-капиталистической идеологии, проекты "партии христианской политики", отклики на злобу дня (с позиций христианского либерально-консервативного центризма) и прочее. Особое русло составляет тема России, разрешаемая, вслед за Достоевским и Соловьевым, на путях христианской историософии. Мысль Булгакова тесно слита с судьбой страны, и, вслед за трагическими перипетиями этой судьбы, его взгляды сильно меняются. Начало первой мировой войны отмечено славянофильскими статьями, полными веры во всемирное призвание и великое будущее державы. Но уже вскоре, в диалогах "На пиру богов" и других текстах революционного периода, судьба России рисуется в ключе апокалиптики и тревожной непредсказуемости, с отказом от всяких рецептов и прогнозов: краткое время Булгаков считал, что католичество лучше православия сумело бы помешать процессам раскола и разложения, подготовившим катастрофу нации (диалоги "У стен Херсониса", 1922, опубл.: "Символ", 1991, № 25). В поздний период в его публицистике остаются по преимуществу лишь церковные и религиозно-культурные темы.
+
+Учение отца Сергия прошло в своем развитии два этапа, философский (до изгнания с родины) и богословский, которые, разнясь по форме и отчасти по источникам, влияниям, вместе с тем прочно связаны единством ведущих интуиций и центральных понятий. На всем своем пути это есть учение о Софии и Богочеловечестве, христианское учение о мире и его истории как воссоединении с Богом. Важнейший движущий мотив учения оправдание мира, утверждение ценности и осмысленности его бытия. При этом, полемизируя с традицией немецкого идеализма, Булгаков отказывается рассматривать разум и мышление в качестве высшего начала, наделенного исключительной прерогативой связи с Богом: предмет утверждения мир во всей его материально-телесной полноте. Оправдание мира предполагает т.о. оправдание материи, и тип своего филоского мировоззрения Булгакова иногда определял взятым у Владимира Соловьева сочетанием "религиозный материализм". В парадигмах христианской мысли задача философии требует выполнения двух последовательных заданий: необходимо раскрыть связь мира и Бога, а затем, всюду руководясь этой связью, развить собственно учение о мире, трактовку материи, телесности и других начал здешнего бытия. Таков логический порядок учения Булгакова; но исторически был обратным ему: мысль отца Сергия развивалась "снизу", от экономической проблематики и философского учения о хозяйстве ("Философия хозяйства") к общему учению о материи и о мире, уже въявь опирающемуся на определенные постулаты о связи мира и Бога, но еще не делающему сами эти постулаты предметом особого анализа ("Свет Невечерний"), и, наконец, к развернутой богословской системе, дающей окончательное решение исходной задачи: укореняющей мир в Боге и вместе с тем прямо следующей христианскому откровению и догматике.
+
+Поскольку мир в христианской онтологии тварное бытие, то учение о мире начинается у Булгакова с учения о творении. Суть тварности раскрывает вопрос: из чего создан мир? Ответ отца Сергия ортодоксально следует библейской традиции: творение мира творение из ничто, чистого небытия и несуществования. Булгаков прослеживает историю понятия Ничто и его применений в онтологии и, отвергая применения явно или скрыто пантеистические, выделяет определенную линию, от [[Платон|Платона]] до Шеллинга, на идеях которой строит свою концепцию. Тварное бытие трактуется им как особый род ничто, наделенный производящими потенциями, чреватый бытием, превращением в нечто. Это соответствует платоновскому и неоплатоническому понятию меона, или относительного небытия; чистое же ничто, всецелая противоположность бытию, передается понятием укона, радикального отрицания бытия. Т.о. возникает (уже выдвигавшаяся у позднего Шеллинга в "Изложении филоского эмпиризма") философема о творении мира как превращении или подъятии укона в меон творческим актом Бога.
+
+Далее строится концепция материи, где Булгаков отчасти следует "Тимею" Платона. Как бытие, погруженное в водоворот возникновения и уничтожения, переходов и превращений, тварное бытие есть "бывание". Но за множественностью и многоликостью бывания необходимо предполагать единую подоснову, в лоне которой только и могут совершаться все возникновения и превращения. Эта универсальная подоснова ("субстрат") бывания, из которой непосредственно возникает все возникающее, все вещи мира, и есть материя. Булгаков принимает относящиеся к ней положения античной традиции. Материя "третий род" бытия, наряду с вещами чувственного мира и их идеальными первообразами, идеями. Она есть неоформленная, неопределенная "первоматерия", materia prima потенциально сущее, способность выявления в чувственном. В своем онтологическом существе она, как и тварное бытие вообще, есть меон, "бытие небытие". Но эти положения дополняются другими, связанными, в первую очередь, с рождающей ролью материи. По Булгакову, она выступает как "Великая МатерьЗемля" древних языческих культов [[Древняя Греция|Греции]] и [[Древний Восток|Востока]], а также "земля" первых стихов Книги Бытия. "Земля" и "мать" ключевые определения материи у Булгакова, выражающие её зачинающую и родящую силу, её плодотворность и плодоносность. Земля "насыщена безграничными возможностями"; она есть "всематерия, ибо в ней потенциально заключено все" (Свет Невечерний. М„ 1917, с. 240-241). Хотя и после Бога, по Его воле, но материя есть также творческое начало. Вслед за Григорием Нисским Булгаков рассматривает бытие мира как процесс прямо продолжающий источный творческий акт Бога непрестанно длящееся творение, совершаемое при непременном активном участии самой материи. Здесь концепция Булгакова оказывается на почве патристики, расходясь с платонизмом и неоплатонизмом; окончательный же свой смысл она получает в контексте христологии и мериологии. Земля-мать не просто рождает, изводит из своих недр все сущее. На вершине своего рождающего и творческого усилия, в его предельном напряжении и предельной чистоте, она потенциально является "Богоземлей" и Богоматерью. Из недр ее происходит Мария и земля становится готовою приять Логоса и родить Богочеловека. Земля становится Богородицей и только в этом истинный апофеоз материи, взлет и увенчание се творческого усилия. Здесь ключ ко всему "религиозному материализму" Булгакова.
+
+Дальнейшее развитие учения о мире требует уже большей конкретизации связи мира и Бога, что доставляют концепции Софии и софийности. В своей зрелой форме они представлены в богословской системе Булгакова, на пути к которой лежал еще один промежуточный этап: критика философии ("Трагедия философии"). К его появлению привело (явное и для самого Булгакова, и для его критиков) расхождение между церковно-православными корнями его метафизики и тем философским языком, методом, который эта метафизика использовала и который принадлежал львиной долей классическому немецкому идеализму. В "Трагедии философии" предлагается новая интерпретация систем европейской философии. В её основе утверждаемое Булгаковым соответствие между онтологической структурой и структурой высказывания, языка (ход мысли, предвосхищающий многие позднейшие лингвофилоские построения на Западе).
+
+Булгаков исследует его и в другие книги того же периода, "Философии имени", посвященной апологии имяславия и родственной аналогичным апологиям Флоренского и Лосева. Из соответствия выводится классификация филоская систем, позволяющая увидеть в их главных видах различные монистические искажения догмата троичности, который исключает монизм и требует полного равноправия, единосущия трех начал, соединенных в элементарном высказывании ("Я есмь нечто") и понятых как начала онтол. В итоге история философии предстает как история особого рода тринитарных ересей. Булгаков делает вывод, что адекватное выражение христианской истины принципиально недоступно для философии и достижимо лишь в форме догматического богословия.
+
+Началом богословского этапа Булгакова служат обширные штудии церковного учения о Святой Троице, Божественной Ипостаси и Премудрости Божией (статья "Ипостась и ипостасность", 1925: "Главы о троичности", 1928, 1930; кн. "Купина неопалимая", 1927). На этой основе, на смену ранним дореволюцонным вариантам, отец Сергий выдвигает в "Агнце Божием" (Париж, 1933) свое окончательное учение о Софии, завершаемое затем в "Утешителе" (1936) и "Невесте Агнца" (1945). Как во всех опытах софиологии (ср. Соловьев, Флоренский), София Премудрость Божия начало, посредствующее меж Богом и миром, "мир в Боге", предвечно сущее в Боге собрание идеальных первообразов всего сущего, аналог платоновского мира идей. Однако все софиологические учения имеют спорный и сомнительный статус, ибо все попытки введения Софии в христианское учение о Боге до сих пор вызывали сильные догматические возражения. В учении Булгакова София сближается с Усией, Сущностью Триединого Бога: "Божественная София есть ... природа Божия, усия, понимаемая ... как раскрывающееся содержание, как Всеединство" (Агнец Божий, с. 125). Данное решение, как и многие его следствия, также вызвало возражения и полемику; в 1935 г. учение Булгакова было осуждено в указах Московской Патриархии, а также зарубежного Архиерейского Собора в Карловицах. В.Н.Лосский. критически анализируя учение, находит, что суть его "поглощение личности софийно-природным процессом, уничтожающим свободу, замена Промысла, предполагающего нравственно-волевое отношение личностей, природно-софийным детерминизмом" (Спор о Софии. Париж, 1936, с. 82). Отец Сергий отвечал оппонентам, и "спор о Софии" не получил окончательного решения по сей день, хотя надо отметить, что учение Булгакова не привлекло на свою сторону практически никого из богословов.
+
+Вместе с тем, помимо своего софианского ядра, система Булгакова содержит немало плодотворных идей и разработок. В согласии с концепцией Богочеловечества она развивает учение о мировом процессе, который во всей целокупности, от акта творения, через пребывание в падшести и до финального Преображения, представляется как "Богочеловеческий процесс", воссоединение твари с Богом. В этих рамках возникает целый ряд частных учений о различных сторонах жизни мира. Раньше и полнее всего у Булгакова развито учение о хозяйстве, в сферу которого включается и экономическая, и научно-техническая деятельность человека. Отражая двойственную природу падшего бытия, хозяйство совмещает в себе свободной творческий "труд познавания и действия", в котором раскрывается софийность мира, и "рабство ничто", служение рожденной падением природной необходимости. Важное место в Богочеловеческом процессе принадлежит искусству. Булгаков трактует его как способность увидеть и показать софийность мира, ибо одно из главных имен Софии Красота. Но как все в падшем бытии, искусство несет и печать ущербности: оно стремится и не может стать теургией, действенным преображением мира. Аналогично анализируются феномены пола, творчества, власти и другие: Булгаков усматривает всюду как софийное, благое начало, так и печать падшести, небытия. В последние годы сюда присоединяется анализ "последних вещей", смерти (Софиология смерти // Вестн. РСХД. 1978, № 127; 1979, № 128) и конца мира (эсхатология "Невесты Агнца").
+
+Рассматривая мир под знаком динамики, процесса, учение Булгакова о мире представляется в целом как теология истории, где в центре находится София как Церковь, поскольку "Церковь действует в истории как творящая сила" (Невеста Агнца, с. 362), и Богочел. процесс может быть понят как становление всего мироздания Церковью. В общем же своем типе и облике, в ряде ведущих мотивов и идей его система напоминает большие теологические системы современного западного христианства, сближаясь с учениями Тейяра де Шардена и, несколько меньше, Тиллиха.
+
+Источник первоначальной версии статьи:
+С. Хоружий (Русская философия. Малый энциклопедический словарь, М., 1995.)
+
+'''Внешние ссылки'''
+
+*[http://www.vehi.net/bulgakov/index.html Сергей Николаевич Булгаков]
+*[http://www.vehi.net/vehi/index.html "Вехи"]
+*[http://www.vehi.net/bulgakov/svet/index.html "Свет Невечерний"] \ No newline at end of file
diff --git a/vendor/wikimedia/utfnormal/scripts/testdata/tokyo.txt b/vendor/wikimedia/utfnormal/scripts/testdata/tokyo.txt
new file mode 100644
index 00000000..1b3fd72f
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/testdata/tokyo.txt
@@ -0,0 +1,587 @@
+[[世界]] > [[アジア]] > [[東アジア]] > [[日本]] > [[関東地方]] > '''東京都'''
+{| align="right" border="1" cellspacing="0" cellpadding="2" style="border-style: solid; border-width: 2px; border-color: #000000; margin-left: 16px; margin-bottom: 16px"|
+|-
+| colspan="2" align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|東京都のデータ
+|-
+| colspan=2 align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 1px; border-color: #000000"|[[画像:Tokyo-pref_Small.png|center|東京都の位置]]
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|面積
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|2,187.08km&sup2;
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|総人口
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|12,379,928人<br>(2003年12月1日現在)
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|県(都)内総生産
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|852,296億円<br>(2000年現在:全国第一位)
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|[[ISO 3166-2]]
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|JP-13
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|都民の日
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|[[10月1日]]
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|都の花
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|[[ケシ]]
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|都の木
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|[[イチョウ]]
+|-
+| align="center" valign="middle" style="border-style: none none none none; border-width: 0 0 0 0; border-color: #000000; background: #f3f3f3"|都民の鳥
+| align="left" valign="middle" style="border-style: none none none solid; border-width: 0 0 0 1px; border-color: #000000"|[[ユリカモメ]]
+|-
+| align="center" valign="middle" style="border-style: none none solid none; border-width: 0 0 1px 0; border-color: #000000; background: #f3f3f3"|知事
+| align="left" valign="middle" style="border-style: none none solid solid; border-width: 0 0 1px 1px; border-color: #000000"|
+|-
+| colspan=2 align="center" style="border-style: solid none solid; background: #f0f0f0"|東京都庁
+|-
+| align="center" style="border-style: solid none solid; background: #f0f0f0"|所在地
+| style="border-style: solid none none solid"|〒163-8001
+|-
+| colspan=2 align="right" style="border-style: none none solid"|東京都新宿区西新宿2-8-1
+|-
+| align="center" style="border-style: none none solid; background: #f0f0f0"|電話番号
+| style="border-style: none none solid solid"|03-5321-1111
+|-
+| align="center" style="border-style: none none solid; background: #f0f0f0"|外部リンク
+| style="border-style: none none solid solid"|[http://www.metro.tokyo.jp/ 東京都庁]
+|}
+
+'''東京都'''(とうきょうと)は、[[日本]]の[[首都]]機能がある[[東京]]を管轄する[[地方自治体]]である。
+
+人口は12,379,928人(2003年12月1日現在)。日本の政治、行政、経済の中心。[[1943年]]に東京都制(昭和18年法律第89号)が施行され 、[[東京府]]と[[東京市]]が統合して成立した。その後、[[地方自治法]]の施行によって東京都制は廃止されたが、「東京都」の名称は引き継がれ、現在に至っている。東京都は府県相当の機能と23の[[東京都特別区|特別区]]の区域における市としての機能とを併せ持つ広域的地方公共団体である。
+
+
+== 地理 ==
+[[画像:Tokyo-pref.png|thumb|市区町村境界図(諸島部を除く)]]
+* [[関東平野]]に位置し、[[東京湾]]に面している。東京都の行政区には、ほかに[[小笠原諸島]]と[[伊豆諸島]]を含む。
+* [[関東地方]]
+* 日本の最南端([[沖ノ鳥島]])、および最東端([[南鳥島]])を持つ。
+*地域としては[[東京都特別区|特別区(23区)部]]と[[三多摩]]および[[島嶼]]([[伊豆諸島]]・[[小笠原諸島]])に分けられる。
+=== [[東京都特別区|特別区(23区)]] ===
+*東部には[[隅田川]]・[[荒川 (関東)|荒川]]・[[江戸川]]等の流域に低地が広がり、[[下町]]と呼ばれている。
+*西側から[[武蔵野台地]]の末端部である幾つもの[[舌状台地]]が伸びており、[[山の手]]と呼ばれる。
+*南部の[[多摩川]]沿いの地域には低地が続いている。
+*臨海部には埋立地が続いている。
+
+=== [[三多摩|三多摩地域]] ===
+*[[多摩川]]沿いの低地を中心として北側には武蔵野台地、南側には[[多摩丘陵]]が広がる。
+*三多摩西部(西多摩)には[[関東山地]]に含まれる山地がある。
+
+=== 島嶼 ===
+
+===隣接都道府県===
+[[千葉県]] - [[神奈川県]] - [[山梨県]] - [[埼玉県]]
+
+
+
+==歴史==
+''[[江戸]]の歴史も参照。''
+
+現代の東京都の領域は古代の[[武蔵国]][[多摩郡|多麻郡]]、荏原郡、豊島郡と足立郡の一部及び[[下総国]]葛飾郡の一部に相当する。近世初期に[[隅田川]]から[[利根川]](現代の[[江戸川]]下流)の間が武蔵国葛飾郡として編入された。武蔵国は現代の東京都だけでなく、[[埼玉県]]と[[神奈川県]]東北部を含む大国であるが、[[国府]]は現代の東京都[[府中市 (東京都)|府中市]]にあった。古代の武蔵国は[[東山道]]に属していたが、771年に[[東海道]]所属に変更された。
+
+[[延喜式]]神名帳には足立郡に[[氷川神社]](名神大社)、[[多摩郡|多磨郡]]に小野神社(一ノ宮)、阿伎留神社、青渭神社などが見えるが、後世武蔵国総社とされた大国御魂神社や東京の神社として著名な[[神田明神]]、[[日枝神社]]の名は見えない。
+
+かなり古い時代から[[渡来人]]が住んでいたようで、亀塚古墳のある狛江郷(現・[[狛江市]]周辺)は[[高句麗]]に由来するとされ、他にも[[渡来人]]にまつわる伝承は多い。[[武蔵野台地|武蔵野]]の開発は渡来人の灌漑技術によるところが大きいとされる。
+
+[[中世]]には[[武蔵七党]]と呼ばれる武士団が興り、源氏の家人となった。源平合戦では豊島氏、足立氏、葛西氏らが活躍している。12世紀には豊島郡[[江戸]]郷の名が見え、この地を本拠とする江戸氏も興った。[[14世紀]]には[[新田義貞]]が[[鎌倉幕府]]軍を破った分倍河原の戦いが名高い。
+
+戦国時代には豊島氏の家宰であった太田氏が台頭し、[[江戸城]]を築いた[[太田道灌]]が武蔵の掌握に力を注いだが暗殺され、[[小田原城]]を拠点とする[[後北条氏]]が武蔵に進出する。[[北条氏照]]は八王子城を築き北方の上杉氏に備えた。その北条氏も[[豊臣秀吉]]の[[小田原攻め]]によって滅び、三河の[[徳川家康]]が関東に転封、江戸城に入る。やがて[[豊臣氏]]を滅ぼした徳川家康は[[江戸]]に幕府を開き、ここに江戸は日本の政治の中心となる。([[江戸時代]])
+
+[[1867年]][[徳川慶喜]]が[[大政奉還]]を行うと、翌年江戸は東京と改称され、[[1869年]][[明治天皇]]が[[皇居]](旧・江戸城)に入ると名実ともに近代日本の首都となった。全国から新政府に仕える人々が集まり、多くは皇居周辺(後世の[[山手線]]内側)に住んだ。これが山の手族の起源である。また[[築地]]には外国人居留地が設けられ、[[銀座]]には西洋風のレンガ街が作られて文明開化が進んだ。
+
+大正期に入ると東京への人口流入はさらに進み、[[1920年]]の人口は370万人になったが、[[1923年]][[関東大震災]]に襲われ、特に下町が大打撃を受けた。さらに[[第二次世界大戦]]末期の[[東京大空襲]]によって市街地の大半が焼け野原となった。しかし[[東京オリンピック]]によって戦後復興は終わりを告げ、東京は[[高度経済成長]]のなかで新日本の政治・経済の中心として大発展を遂げる。1980年代後半には[[バブル景気]]によって異常な地価高騰に見舞われた。[[1991年]]には[[新宿]]に都庁新庁舎が完成し、東京の新たなシンボルとなった。その後は[[バブル崩壊]]が進み経済は低迷している。
+
+===古代===
+* 771年 [[武蔵国]]、[[東山道]]から[[東海道]]に転属
+* 939年 [[平将門]]の乱(~940年)
+===中世===
+*1333年 [[新田義貞]]、分倍河原で[[鎌倉幕府]]軍を破る
+*1416年 [[上杉禅秀]]の乱
+*1475年 この頃、[[太田道灌]]江戸城を築く
+*1486年 太田道灌、相模の上杉定正の館で暗殺される
+*1524年 北条氏綱、江戸城に入城
+*1582年 この頃、北条氏康八王子城を本格築城
+*1590年 豊臣軍の前に江戸城開城、八王子城落城。[[徳川家康]]、関東入部。
+===江戸時代===
+*1603年 徳川家康、[[征夷大将軍]]に任じられ、江戸開府。
+*1635年 [[武家諸法度]]、[[参勤交代]]の制を定める
+*1651年 由比正雪の乱
+*1657年 江戸大火。[[吉原遊郭]]、[[浅草]]に移転(新吉原)
+*1702年 [[赤穂浪士]]、吉良邸討ち入り
+*1707年 富士宝永山噴火、江戸にも降灰。 
+*1714年 絵島事件
+*1723年 火の見櫓設置を指示
+*1727年 武蔵野新田開発
+*1783年 浅間山噴火、江戸にも降灰。
+*1791年 男女混浴禁止令
+*1805年 [[関東取締出役]]設置
+*1836年 江戸名所図会刊行完了
+*1839年 [[蛮社の獄]]
+*1840年 [[遠山景元]]、町奉行となる
+*1853年 [[マシュー・ペリー|ペリー]]来航
+*1854年 ペリー再航、[[日米和親条約]]
+*1855年 江戸大地震
+*1859年 横浜開港
+*1860年 [[桜田門外の変]]
+*1866年 江戸各所で打ち壊し
+*1867年 [[大政奉還]]
+*1868年 [[彰義隊]]の変(5月)/江戸を東京と改称(7月)/明治改元(9月)/[[明治天皇]]、東京に行幸(10月)
+
+===近代以降===
+*1869年 明治天皇再び東京に行幸、東京城を[[皇居]]とする。
+*1871年 [[廃藩置県]]、東京府設置。
+*1874年 東京[[警視庁]]設置
+*1882年 [[上野動物園]]完成
+*1889年 市町村制施行され、[[東京市]]誕生。
+*1905年 [[日比谷公園]]事件
+*1923年 [[関東大震災]]
+*1927年 地下鉄開通
+*1936年 [[国会議事堂]]落成
+*1942年 東京初空襲
+*1943年 [[東京都]]成立
+*1945年 [[東京大空襲]](3月)/米軍進駐(9月)
+*1958年 [[東京タワー]]完成
+*1964年 [[東海道新幹線]]開通(10月1日)/[[東京オリンピック]](10月10日~24日)
+*1968年 [[小笠原諸島]]返還
+*1968年 [[三億円事件]]([[府中市 (東京都)|府中市]])
+*1970年 [[三島由紀夫]]決起事件
+*1974年 三菱重工ビル爆破事件
+*1977年 立川基地全面返還([[立川市]])
+*1978年 新東京国際空港(現[[成田国際空港]])開港、[[東京国際空港]](羽田空港)は原則国内線専用に。
+*1986年 地価急騰、狂乱地価。
+*1988年 [[東京ドーム]]完成
+*1991年 東京の電話局番4桁になる(1月)/新宿に[[東京都新庁舎|東京都庁新庁舎]]完成(3月)
+*1993年 東京湾連絡橋[[レインボーブリッジ]]完成
+*1995年 [[地下鉄サリン事件]]/[[東京メトロポリタンテレビジョン]](MXテレビ)開局(11月)
+*1998年 [[山一證券]]倒産(バブル崩壊)
+*1999年 [[石原慎太郎]]都知事初当選(現職知事)
+
+===行政区域の変遷===
+* [[1868年]](慶応 4)- [[東京]]と命名([[江戸]]からの名称変更)。
+* [[1872年]](明治 5)- [[東京府]]が現23区の範囲に拡大。
+* [[1889年]](明治22)- 市制・町村制が施行され、新たに[[東京市]]を設置(15区設置)。
+* [[1893年]](明治26)- [[三多摩]]地方が東京府に編入。
+* [[1898年]](明治31)- 東京市の「市政特例」が廃止され、一般市制に移行し東京市役所が開設(移行日である10月1日は[[1952年]](昭和27)から「都民の日」とされる。)。
+* [[1932年]](昭和 7)- 5郡(荏原郡、豊多摩郡、北多摩郡、南足立郡及び南葛飾郡)82町村を東京市に編入。東京市は35区になる。
+* [[1943年]](昭和18)- 東京府と東京市を統合、東京都と改称。
+* [[1947年]](昭和22)- [[地方自治法]]施行。区は23区となり、特別地方公共団体である「特別区」になる。
+
+== 人口 ==
+===年齢構成===
+<div style="width:50%;float:left"><!-- 1pix=1,000x0.095 -->
+年齢5歳階級別人口<br/>
+2003年10月1日現在推計人口<br/>
+総計 [単位 千人]
+{|
+|-
+! 年齢
+! 人口
+|-
+|0~4歳
+|[[画像:g30.png]][[画像:g10.png]][[画像:g05.png]][[画像:g01.png]] 490
+|-
+|5~9
+|[[画像:g30.png]][[画像:g10.png]][[画像:g03.png]][[画像:g01.png]] 473
+|-
+|10~14
+|[[画像:g30.png]][[画像:g10.png]][[画像:g05.png]][[画像:g03.png]] 513
+|-
+|15~19
+|[[画像:g50.png]][[画像:g10.png]][[画像:g01.png]] 651
+|-
+|20~24
+|[[画像:g50.png]][[画像:g30.png]][[画像:g01.png]] 860
+|-
+|25~29
+|[[画像:g50.png]][[画像:g30.png]][[画像:g10.png]][[画像:g05.png]][[画像:g03.png]][[画像:g01.png]] 1045
+|-
+|30~34
+|[[画像:g100.png]][[画像:g05.png]][[画像:g01.png]] 1116
+|-
+|35~39
+|[[画像:g50.png]][[画像:g30.png]][[画像:g10.png]][[画像:g01.png]] 968
+|-
+|40~44
+|[[画像:g50.png]][[画像:g10.png]][[画像:g10.png]][[画像:g05.png]][[画像:g01.png]] 800
+|-
+|45~49
+|[[画像:g50.png]][[画像:g10.png]][[画像:g05.png]][[画像:g01.png]][[画像:g01.png]] 710
+|-
+|50~54
+|[[画像:g50.png]][[画像:g30.png]][[画像:g01.png]][[画像:g01.png]] 871
+|-
+|55~59
+|[[画像:g50.png]][[画像:g30.png]][[画像:g01.png]] 858
+|-
+|60~64
+|[[画像:g50.png]][[画像:g10.png]][[画像:g10.png]][[画像:g05.png]] 794
+|-
+|65~69
+|[[画像:g50.png]][[画像:g10.png]][[画像:g05.png]][[画像:g01.png]] 699
+|-
+|70~74
+|[[画像:g50.png]][[画像:g03.png]][[画像:g01.png]] 569
+|-
+|75~79
+|[[画像:g30.png]][[画像:g05.png]][[画像:g03.png]][[画像:g01.png]] 417
+|-
+|80歳以上
+|[[画像:g30.png]][[画像:g10.png]][[画像:g05.png]] 476
+|}
+</div>
+<div style="width:50%;float:left">
+年齢5歳階級別人口<br/>
+2003年10月1日現在推計人口<br/>
+男女別 [単位 千人]
+{|
+|-
+! align=right|男
+! align=center|年齢
+! 女
+|-
+| align=right|251 [[画像:g10.png]][[画像:g10.png]][[画像:g03.png]]
+| align=center|0~4歳
+|[[画像:r10.png]][[画像:r10.png]][[画像:r01.png]][[画像:r01.png]] 239
+|-
+| align=right|242 [[画像:g10.png]][[画像:g10.png]][[画像:g01.png]][[画像:g01.png]]
+| align=center|5~9
+|[[画像:r10.png]][[画像:r10.png]][[画像:r01.png]] 231
+|-
+| align=right|260 [[画像:g10.png]][[画像:g10.png]][[画像:g03.png]][[画像:g01.png]]
+| align=center|10~14
+|[[画像:r10.png]][[画像:r10.png]][[画像:r03.png]][[画像:r01.png]] 253
+|-
+| align=right|334 [[画像:g30.png]][[画像:g01.png]]
+| align=center|15~19
+|[[画像:r30.png]] 317
+|-
+| align=right|446 [[画像:g30.png]][[画像:g10.png]][[画像:g01.png]][[画像:g01.png]]
+| align=center|20~24
+|[[画像:r30.png]][[画像:r05.png]][[画像:r03.png]][[画像:r01.png]] 414
+|-
+| align=right|545 [[画像:g50.png]][[画像:g01.png]]
+| align=center|25~29
+|[[画像:r30.png]][[画像:r10.png]][[画像:r05.png]][[画像:r01.png]][[画像:r01.png]] 500
+|-
+| align=right|573 [[画像:g50.png]][[画像:g03.png]][[画像:g01.png]]
+| align=center|30~34
+|[[画像:r50.png]][[画像:r01.png]] 543
+|-
+| align=right|504 [[画像:g30.png]][[画像:g10.png]][[画像:g05.png]][[画像:g01.png]][[画像:g01.png]]
+| align=center|35~39
+|[[画像:r30.png]][[画像:r10.png]][[画像:r03.png]][[画像:r01.png]] 464
+|-
+| align=right|415 [[画像:g30.png]][[画像:g05.png]][[画像:g03.png]][[画像:g01.png]]
+| align=center|40~44
+|[[画像:r30.png]][[画像:r05.png]][[画像:r01.png]] 385
+|-
+| align=right|364 [[画像:g30.png]][[画像:g03.png]][[画像:g01.png]]
+| align=center|45~49
+|[[画像:r30.png]][[画像:r01.png]][[画像:r01.png]] 346
+|-
+| align=right|442 [[画像:g30.png]][[画像:g10.png]][[画像:g01.png]]
+| align=center|50~54
+|[[画像:r30.png]][[画像:r10.png]] 429
+|-
+| align=right|426 [[画像:g30.png]][[画像:g10.png]]
+| align=center|55~59
+|[[画像:r30.png]][[画像:r10.png]][[画像:r01.png]] 432
+|-
+| align=right|383 [[画像:g30.png]][[画像:g05.png]][[画像:g01.png]]
+| align=center|60~64
+|[[画像:r30.png]][[画像:r05.png]][[画像:r03.png]][[画像:r01.png]] 411
+|-
+| align=right|330 [[画像:g30.png]][[画像:g01.png]]
+| align=center|65~69
+|[[画像:r30.png]][[画像:r05.png]] 369
+|-
+| align=right|261 [[画像:g10.png]][[画像:g10.png]][[画像:g03.png]][[画像:g01.png]]
+| align=center|70~74
+|[[画像:r10.png]][[画像:r10.png]][[画像:r05.png]][[画像:r03.png]][[画像:r01.png]] 308
+|-
+| align=right|180 [[画像:g10.png]][[画像:g05.png]][[画像:g01.png]][[画像:g01.png]]
+| align=center|75~79
+|[[画像:r10.png]][[画像:r10.png]][[画像:r01.png]][[画像:r01.png]] 237
+|-
+| align=right|163 [[画像:g10.png]][[画像:g05.png]]
+| align=center|80歳以上
+|[[画像:r10.png]][[画像:r10.png]][[画像:r05.png]][[画像:r03.png]][[画像:r01.png]] 313
+|}
+</div>
+<br style="clear:left">
+*データ出典:[http://www.stat.go.jp/data/jinsui/2003np/index.htm 第10表/都道府県, 年齢(5歳階級), 男女別人口-総人口]<br/>(総務省統計局)
+
+== 行政組織 ==
+* 東京都 - 東京都域全体の広域行政と[[特別区]]の区域における大都市行政。
+** 議決機関
+*** 都議会
+** 執行機関
+*** [[知事]]
+**** [[副知事]]
+***** 知事部局
+***** [[東京消防庁]]
+***** 公営企業
+**** 出納長
+*** 行政委員会
+* 特別地方公共団体([[特別区]]): [[千代田区]]、[[中央区 (東京都)|中央区]]、[[台東区]]、。。。など23区
+* 特別区の一部事務組合 (3団体)
+* 普通地方公共団体([[市町村]])
+* [[市町村]]の一部事務組合 (34団体)
+* [[財産区]](1市2町に8つの財産区)
+
+[[画像:tokyotower.jpg|200px|thumb|東京タワーと増上寺]]
+
+== 経済 ==
+江戸時代、江戸(東京)は行政府の置かれている場所として経済的にも重要な地であり、人口100万人を超えていたと言われる日本最大の消費地であった。しかし「日本の富の7分は大坂に」と言われるように経済の中心地は[[大坂]]であり、今日のように東京が経済の中心地となったのは[[明治維新]]以降である。明治政府は、欧米諸国へのキャッチアップのため東京への一極集中を進め、その流れは[[太平洋戦争]]後も引き継がれた。[[高度経済成長|高度経済成長期]]にはこの流れが加速され、[[バブル経済|バブル経済期]]に最高潮に達した。その結果、[[国内総生産]]における東京都が占める割合は16%程度となり、全国の証券取引所における証券取引の約8割が[[東京証券取引所]]を占めるなど、日本経済において東京は圧倒的な地位を占めるようになった。近年では、過度の集中に対する反省から、[[首都機能移転]]が議論されており、本社機能を東京から移転する企業も出てくるなど、経済における東京の一極集中を是正しようとする傾向にある。
+
+都内総生産(名目)は84兆7628億円(平成13年度、東京都)で、日本の国内総生産の16.9%(平成13年度、東京都)を占めている。この比率は最近10年間においてほとんど変わっていない。国内経済の低迷に伴い、都内経済も低迷傾向にあるが、国内景気ほど落ち込んではおらず、今後は国内経済の回復に伴って都内経済も回復すると見込まれている。
+
+== 産業 ==
+東京都内総生産の産業別構成比は、第1次産業が0.1%、第2次産業が18.6%、第3次産業が91.5%である(平成13年度。この他に控除すべき数値があるため合計は100%を超える)。このように、第1次産業が占める割合は極めて少なく、第3次産業が占める割合が高く、特にサービス業・卸、小売業の比率が高い。また、首都と言う性格上大手企業の本社が数多くあり、本社機能の提供するサービスも高い比率を占める。そのため、東京都の産業連関表では「財(農林水産業、鉱業、製造業、建設及び電気・ガス・水道)・サービス・本社」という三部門に分かれている。
+=== 第1次産業 ===
+* 農業
+東京都の耕地面積は 8460ha(平成15年、農林水産省)で、全国最低である。農地は[[三多摩|三多摩地域]]に集中している。区部の農地は年々縮小しており、農地がまったくない区も少なくない。東京都では、大消費地に近い地理的特性から、野菜・果樹・花卉が主に生産されており、[[小松菜]]、[[ホウレンソウ]]が主要な生産物である。特に小松菜は中央卸売市場の総入荷量のうち、32.5%(2000年、東京都)を占める。かつては[[練馬大根]]が特産物であったが、現在ではあまり生産されていない。
+* 畜産業
+* 林業
+* 水産業
+=== 第2次産業 ===
+* 製造業
+ 東京都は[[京浜工業地帯]]の一角であり、[[東京湾]]沿岸部を中心に事業所が多く集まる。また、[[日野市]]、[[府中市 (東京都)|府中市]]、[[八王子市]]、[[青梅市]]など、多摩地区にも大型の事業所が多くあり、これら地域の製品出荷額も多い。東京都には幅広い分野の製造業が営まれており、特に[[印刷]]、[[情報通信機械]]の占める割合が多く、これらの分野での製品出荷額は全国一位である(平成14年、東京都)。また、[[皮革]]、[[精密機械]]の出荷額も全国一位である(平成14年、同)。その他、[[電気機械]]、[[輸送用機械]]、[[一般機械]]の出荷額が多い。
+
+* 建設業
+
+=== 第3次産業 ===
+==== 金融・保険 ====
+[[UFJ銀行]]・[[りそな銀行]]・[[埼玉りそな銀行]]を除く[[都市銀行]]の本店が23区内にある。
+
+==== サービス ====
+==== 通信・放送 ====
+*[[日本放送協会|NHK]]が[[渋谷区]]に、[[民間放送|民放]][[テレビ]]の在京[[キー局]]はすべて[[港区 (東京都)|港区]]に、[[東京メトロポリタンテレビジョン]]は[[江東区]]にある。
+
+*[[ラジオ局]]は、NHKは渋谷区、[[TBSラジオ&コミュニケーションズ|TBSラジオ]]、[[J-WAVE]]は[[港区 (東京都)|港区]]、[[ニッポン放送]]と[[東京FM]]は[[千代田区]]、[[文化放送]]は[[新宿区]]にある。
+
+==== 商業 ====
+東京都の商業は、生産額が19兆4627億円(2001年、東京都)であり、都内総生産のうち23.0%(同)を占め、サービス業についで高い割合を占める。
+日本の商業において、東京都の占める割合は大きく、事業所数は10.5%、従業員数は14.3%、販売額は32.2%(2002年、東京都)を占めており、いずれも全国一位である。特に卸売業の占める割合が大きく、事業所数は15.2%、従業者数は22.6%、販売額は38.7%(同)を占めている。事業所、従業員数に比べて販売額が大きいのが特徴で、取扱額が大きい事業所が多いことを示している。小売業は事業所数が9.2%、従業者数が10.2%、販売額が12.4%(同)で、卸売業ほど占める割合が大きくないが、いずれも全国一位である。
+
+東京都の卸売業と小売業を比較すると、事業所数では小売業が卸売業を大幅に上回るが、販売額では、卸売業が小売業に比べて圧倒的な割合を占め、やはり卸売業では取扱額が大きい事業所が多いことが示されている。
+
+===== 卸売業 =====
+東京都の卸売業は、事業所数57,653、販売額は159兆9582億5200万円(2002年、東京都)である。事業所数では、従業員30人以下の小規模な事業所が多いが、販売額は100人以上の大規模事業所が約63%と圧倒的な比率を占める。事業所は区部に集中しており、特に中央区に多い。
+産業小分類別に見ると、機械器具卸売業が販売額41兆3759億8400万円(同)で多数を占め、以下各種商品卸売業、建築材料、鉱物・金属材料等卸売業、飲食料品卸売業、と続く。機械器具卸売業は、電気機械器具卸売業の占める割合が半数以上を占める。各種商品卸売業は、事業所数が149と非常に少ないにもかかわらず、販売額が40兆4902億7300万円であり、非常に規模が大きい事業所があることが示されている。
+
+===== 小売業 =====
+東京都の小売業は、商店数119,016、販売額は16兆7460億3500万円(2002年、東京都)である。商店数は区部に多く、販売額に占める割合も多い。特に中央区、新宿区、渋谷区等、百貨店や家電量販店が集中する繁華街がある地域で販売額が大きい。
+
+==== 不動産 ====
+
+== 姉妹友好都市 ==
+* [[ニューヨーク (市)|ニューヨーク]]([[アメリカ合衆国]])
+* [[北京]]([[中華人民共和国]])
+* [[パリ]]([[フランス]])
+* [[ニューサウスウェールズ州]]([[オーストラリア]])
+* [[ソウル特別市]]([[大韓民国]])
+* [[ジャカルタ]]([[インドネシア共和国]])
+* [[サンパウロ州]]([[ブラジル連邦共和国]])
+* [[カイロ県]]([[エジプト・アラブ共和国]])
+* [[モスクワ]]([[ロシア連邦]])
+* [[ベルリン]]([[ドイツ連邦共和国]])
+* [[ローマ]]([[イタリア共和国]])
+
+== 地域 ==
+*以下の26市・23区・1郡・5町・8村がある。
+
+=== [[東京都特別区|特別区]](23区) ===
+* [[足立区]]
+* [[荒川区]]
+* [[板橋区]]
+* [[江戸川区]]
+* [[大田区]]
+* [[葛飾区]]
+* [[北区 (東京都)|北区]]
+* [[江東区]]
+* [[品川区]]
+* [[渋谷区]]
+* [[新宿区]]
+* [[杉並区]]
+* [[墨田区]]
+* [[世田谷区]]
+* [[台東区]]
+* [[中央区 (東京都)|中央区]]
+* [[千代田区]]
+* [[豊島区]]
+* [[中野区]]
+* [[練馬区]]
+* [[文京区]]
+* [[港区 (東京都)|港区]]
+* [[目黒区]]
+
+=== [[三多摩|三多摩地域]] ===
+==== 市部 ====
+* [[昭島市]]
+* [[あきる野市]]
+* [[稲城市]]
+* [[青梅市]]
+* [[清瀬市]]
+* [[国立市]]
+* [[小金井市]]
+* [[国分寺市]]
+* [[小平市]]
+* [[狛江市]]
+* [[立川市]]
+* [[多摩市]]
+* [[調布市]]
+* [[西東京市]]
+* [[八王子市]]
+* [[羽村市]]
+* [[東久留米市]]
+* [[東村山市]]
+* [[東大和市]]
+* [[日野市]]
+* [[府中市 (東京都)|府中市]]
+* [[福生市]]
+* [[町田市]]
+* [[三鷹市]]
+* [[武蔵野市]]
+* [[武蔵村山市]]
+
+==== 郡部 ====
+* [[西多摩郡]]
+** [[日の出町]] - [[瑞穂町 (東京都)|瑞穂町]] - [[奥多摩町]] - [[檜原村]]
+
+=== 島嶼(とうしょ)部 ===
+* 大島
+** [[大島町 (東京都)|大島町]]([[伊豆大島|大島]]) - [[利島村]]([[利島]]) - [[新島村]]([[新島]]・[[式根島]]) - [[神津島村]]([[神津島]]・[[銭洲]])
+* 三宅島
+** [[三宅村]]([[三宅島]]) - [[御蔵島村]]([[御蔵島]])
+* 八丈島
+** [[八丈町]]([[八丈島]]・[[八丈小島]]) - [[青ヶ島村]]([[青ヶ島]]) - [[鳥島]]・[[須美寿島]]・[[ベヨネース列岩]]・[[孀婦岩]]
+* 小笠原諸島
+** [[小笠原村]]([[小笠原諸島]] - [[硫黄島]] - [[沖ノ鳥島]])
+
+== 交通 ==
+=== 空港 ===
+* [[東京国際空港]](羽田空港)- [[大田区]]にある。国内線を中心として国際線も発着する。
+* 世界から日本の首都東京への連絡には主に[[成田国際空港]](成田空港) が使われる。国際線を中心として国内線も発着。所在地は[[千葉県]] [[成田市]](用地の一部は隣接町)。[[1991年]]3月に空港ターミナル直下に鉄道が乗り入れるようになり、東京都内への連絡は一応整ったとされる(着工したものの完成することがなかった「[[成田新幹線]]」の施設(駅など)を一部活用し、[[JR]]線・[[京成電鉄|京成線]]と接続した。開港時より京成[[スカイライナー]]は運行していたが、当時の成田空港駅(現・[[東成田駅]])はターミナルから少し距離があった)。
+
+=== 鉄道 ===
+;中心となる駅
+:[[東京駅]]、[[上野駅]]、[[品川駅]]、[[渋谷駅]]、[[新宿駅]]、[[池袋駅]]
+
+;山手線内の連絡
+:[[東京地下鉄]]、[[都営地下鉄]]、[[都電荒川線]]、[[山手線]]
+
+;その他主に23区内の連絡
+:[[東京モノレール羽田線|東京モノレール]]、[[京急空港線]]、[[東急池上線]]、[[東急大井町線]]、[[東急世田谷線]]、[[東急多摩川線]]、[[京王新線]]、[[西武有楽町線]]、[[東武亀戸線]]、[[東武大師線]]、[[京成押上線]]、[[京成金町線]]、[[東京臨海高速鉄道りんかい線|りんかい線]]、[[ゆりかもめ東京臨海新交通臨海線|新交通ゆりかもめ]]
+
+;23区内の連絡ならびに近郊との連絡
+*JR
+:[[中央快速線]]、[[埼京線]]、[[湘南新宿ライン]]、[[京浜東北線]]、[[常磐快速線]]、[[常磐緩行線]]、[[中央・総武緩行線]]、[[京葉線]]、[[総武快速線]]、[[横須賀線]]、
+*私鉄
+:[[京急本線]]、[[東急目黒線]]、[[東急東横線]]、[[東急田園都市線]]、[[京王井の頭線]]、[[小田急小田原線]]、[[京王線]]、[[西武新宿線]]、[[西武池袋線]]、[[東武東上本線]]、[[京成本線]]、[[東武伊勢崎線]]、[[北総鉄道北総線]]、[[東京地下鉄東西線]]、[[埼玉高速鉄道線]]、[[都営地下鉄新宿線]]、([[つくばエクスプレス]] ※2005年開業予定)
+
+;三多摩地区の連絡
+*JR
+:[[青梅線]]、[[五日市線]]、[[八高線]]、[[武蔵野線]]、[[南武線]]、[[横浜線]]
+*私鉄
+:[[多摩都市モノレール線|多摩都市モノレール]]、[[京王相模原線]]、[[京王競馬場線]]、[[京王動物園線]]、[[京王高尾線]]、[[小田急多摩線]]、[[西武国分寺線]]、[[西武多摩川線]]、[[西武多摩湖線]]、[[西武拝島線]]
+
+;広範囲な連絡
+*新幹線
+:[[東海道新幹線]]、[[東北新幹線]]、[[上越新幹線]]、[[長野新幹線]]
+*在来線
+:[[東海道本線]]、[[東北本線]]、[[中央本線]]、[[常磐線]]、[[総武本線]]
+
+=== 道路 ===
+;高速道路
+:[[首都高速]]、第1東海自動車道([[東名高速道路]])、[[中央自動車道]]、[[関越自動車道]]、[[首都圏中央連絡自動車道]](圏央道)、[[東京外環自動車道]]、[[第三京浜]]、[[京葉道路]]
+
+;有料道路
+:[[八王子バイパス]]、[[稲城大橋]]、[[ひよどり山有料道路]]
+
+;一般道
+:[[内堀通り]]、[[外堀通り]]、[[外苑東通り]]、[[外苑西通り]]、[[明治通り (東京都)|明治通り]]、[[山手通り]]、[[環七通り]]、[[環八通り]]、[[白山通り]]、[[春日通り]][[川越街道]]([[国道254号]])、[[北本通り]]、[[尾竹橋通り]]、[[本郷通り]]、[[高島通り]]、[[中央通り (東京都)|中央通り]]、[[第一京浜]]([[国道15号]])、[[第二京浜]]([[国道1号]])、[[中原街道]]、[[目黒通り]]、[[玉川通り]]([[国道246号]])、[[世田谷通り]]、[[新宿通り]][[甲州街道]]([[国道20号]])、[[五日市街道]]、[[青梅街道]]、[[新青梅街道]]、[[目白通り]]、[[中仙道]]([[国道17号]])、[[昭和通り]][[日光街道]]([[国道4号]])、[[江戸通り]][[水戸街道]]([[国道6号]])、[[京葉道路]]([[国道14号]])、[[葛西橋通り]]、[[東八道路]]、[[新小金井街道]]、[[府中街道]]、[[鎌倉街道]]、[[東京環状]]([[国道16号]])
+
+===バス===
+*[[小田急バス]] - [[小田急シティバス]]
+*[[神奈川中央交通]]
+*[[関東バス]]
+*[[京王電鉄バス]] - [[京王バス東]] - [[京王バス中央]] - [[京王バス南]]
+*[[京成バス]] - [[京成タウンバス]]
+*[[京浜急行バス]] - [[羽田京急バス]]
+*[[国際興業|国際興業バス]]
+*[[立川バス]] -
+*[[東急バス]] - [[東急トランセ]]
+*[[東京都交通局]]([[都営バス]])
+*[[東武バス]](東武バスセントラル) - [[朝日自動車|朝日バス]]
+*[[西武バス]]
+*[[西東京バス]] - [[多摩バス]]
+*[[日立自動車]]
+*[[東海汽船|東海汽船バス]]
+*三宅村営バス
+*八丈町営バス
+*小笠原村営バス
+
+== 観光 ==
+*[[東京タワー]]
+*[[東京ドーム]]
+*[[お台場]]
+*[[六本木ヒルズ]]
+*[[サンシャインシティ]]
+*[[浅草寺]]
+*[[皇居]]
+*[[明治神宮]]
+*[[上野動物園]]
+*[[柴又帝釈天]]
+
+== 東京副都心(7ヶ所) ==
+東京都が策定した副都心。全部で7箇所ある。
+
+[[新宿副都心]] - [[池袋副都心]] - [[渋谷副都心]] - [[上野・浅草副都心]] - [[錦糸町副都心]] - [[大崎副都心]] - [[東京臨海副都心|臨海副都心]]
+
+== その他 ==
+* [[東京を舞台にした漫画作品]]
+* [[東京を舞台にしたアニメ作品]]
+* [[東京を舞台にした文芸作品]]
+* [[東京を舞台にした映画作品]]
+
+== 関連項目 ==
+* [[東京]]
+* [[江戸]]
+* [[東京臨海副都心]]
+* [[東京都出身の有名人一覧]]
+* [[東京都の都道一覧]]
+* [[グレーター・トウキョウ・フェスティバル]]
+
+== 外部リンク ==
+* [http://www.metro.tokyo.jp/ 東京都ウェブサイト]
+* [http://www.stat.go.jp/data/ssds/5.htm 総務省統計局サイト 社会生活統計指標 -都道府県の指標-2004]
+
+{{日本の都道府県}}
+
+[[Category:日本の都道府県|とうきようと]]
+[[Category:東京都|とうきようと]]
+
+[[ca:Tòquio]]
+[[cy:Tocio]]
+[[da:Tokyo]]
+[[de:Tokio]]
+[[en:Tokyo]]
+[[eo:Tokio]]
+[[es:Tokio]]
+[[et:Tōkyō]]
+[[fi:Tokio]]
+[[fr:Tōkyō]]
+[[it:Tokyo]]
+[[ko:도쿄]]
+[[nl:Tokio]]
+[[no:Tokyo]]
+[[pl:Tokio]]
+[[pt:Tóquio]]
+[[ro:Tokio]]
+[[simple:Tokyo]]
+[[sl:Tokio]]
+[[sv:Tokyo]]
+[[tokipona:ma_tomo_Tokijo]]
+[[tr:Tokyo]]
+[[zh:东京]] \ No newline at end of file
diff --git a/vendor/wikimedia/utfnormal/scripts/testdata/washington.txt b/vendor/wikimedia/utfnormal/scripts/testdata/washington.txt
new file mode 100644
index 00000000..5ca4650f
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/testdata/washington.txt
@@ -0,0 +1,281 @@
+[[Image:Largewashington.jpg|thumb|347px|align=right|Aerial photo of Washington, DC (looking WSW across the National Mall)]]
+'''Washington, DC''', officially the '''District of Columbia''' (also known as '''DC'''; '''Washington'''; and, historically, '''the Federal City''') is the [[capital (government)|capital]] city and administrative district of the [[United States|United States of America]]. Residents of the city and its surrounding [[suburb]]s refer to it simply as '''the District''' or '''DC''', to contrast Washington from its greater metropolitan area.
+
+'''Washington, DC''' is the most common way to refer to the District throughout the rest of the United States and the world. '''Washington''' or '''Washington, DC''' is also used as a [[metonym]] for the federal government. Politicians and candidates for office sometimes use these terms pejoratively to convey a sense of solidarity with their [[constituent]]s by distancing themselves from the negative image of an out-of-touch centralized government. (The [[Washington Post]] criticized this common political tactic in a 2001 [http://www.washingtonpost.com/ac2/wp-dyn?pagename=article&contentId=A22140-2001Aug30&notFound=true editorial].)
+
+The District of Columbia is not part of any [[U.S. State|state]], but rather composes a unique, [[Federal Government of the United States|federally]] managed district within the United States, with limited local rule. As the seat of national government as well as the home of numerous national landmarks, museums, and sports teams, Washington is a popular international destination for [[tourist]]s and school trips.
+
+The centers of all three branches of the U.S. [[federal]] [[government]] are in Washington, as well as the headquarters of most [[Independent Agencies of the United States Government|federal agencies]]. Washington also serves as the headquarters for the [[World Bank]], the [[International Monetary Fund]], and the [[Organization of American States]]. All of this has made Washington the frequent focal point of massive political demonstrations and [[protest]]s, particularly on the [[National Mall]].
+
+The population of Washington, as of [[2003]] U.S. Census Bureau estimates, is 563,384. Despite being smaller in area than the smallest state ([[Rhode Island]]), it has a larger population than the least populous state ([[Wyoming]]). Together with portions of [[Virginia]] and [[Maryland]], and [[Baltimore, Maryland|Baltimore]] and its environs, Washington is part of a large [[metropolitan area]] known as the [[Baltimore-Washington Metropolitan Area]]. In recent years, the metro area has expanded to include communities as far away as [[West Virginia]], [[Delaware]], and [[Pennsylvania]].
+
+The official bird of Washington DC is the [[Wood thrush]]. The official motto is ''Justitia Omnibus'' (Justice for All).
+
+''For non-federal and historical geographical information on the District of Columbia, go to the'' [[District of Columbia (geography)]] ''page.''
+
+== Law and government ==
+
+<div style="float: left; margin-top: 10px; margin-right: 10px; text-align: center;"> [[Image:Dctaxationsample_small.jpg|License plate reading Washington, D.C. at the top and Taxation Without Representation at the bottom]]<br><small>''DC License Plate''</small></div>
+
+Residents of the District vote for the [[President of the United States of America|President]] but do not have voting representation in [[United States Congress|Congress]]. Citizens of Washington are represented in the House of Representatives by a [[Shadow Representative|non-voting Delegate]], who sits on committees and participates in debate, but cannot vote. DC does not have representation in the Senate. Citizens of Washington, DC are thus unique in the world, as citizens of the capital city of every other country have the same representation rights as their fellow citizens.
+
+There have been efforts to attain voting representation for many years, including the [[District of Columbia Voting Rights Amendment]] passed by Congress in 1978 but unratified by the states. These efforts are endorsed by the current Mayor, [[Anthony Williams]] and by the current Delegate, [[Eleanor Holmes Norton]]. So, while the District's official motto is "Justitia omnibus" (Justice to all), the words "Taxation Without Representation" were added to DC license plates in 2000 and there is a current movement to the add the words "[[No Taxation Without Representation]]" to the DC flag. Advocates of statehood who have supported these changes have said that they are intended as a protest and to raise awareness in the rest of the country. These measures in particular were chosen because the DC flag is one of the few things under direct local control without requiring approval from Congress.
+
+[[Image:DCFlag.PNG|thumbnail|right|300px|The flag of Washington, DC is based on the [[coat of arms]] borne by the [[England|English]] ancestors of [[George Washington]].]]
+
+Various approaches for attaining voting representation in Congress have been proposed.
+These include:
+# Treating DC in some way as a state:
+## Have Congress pass legislation that would treat DC as if it were a state for the purposes of voting representation in Congress. Senator Joseph Lieberman introduced The No Taxation Without Representation Act of 2003 (S. 617) on March 13, 2003, to the U.S. Senate, and Congresswoman Eleanor Holmes Norton introduced the same Act in the U.S. House of Representatives (H.R. 1285).
+## Amend the U.S. Constitution. In 1978 an amendment to the U.S. Constitution that would have given full congressional voting representation to residents of the District of Columbia passed through both the U.S. Senate and the U.S. House of Representatives. However, by 1985 when the seven year limit on ratification of the amendment expired, the amendment had only passed in 16 of 38 states necessary.
+## Statehood for the District of Columbia. Statehood for DC was last discussed in the U.S. House of Representatives in November 1993, and was defeated by the vote of 277 to 153.
+# (Re)combining DC with Maryland in some way:
+## Retrocession (Reunion with the State of Maryland). The original land of DC was originally Maryland and Virginia's land, and from 1790 until 1801 citizens living in DC continued to vote for, and even run as, candidates for the U.S. Congress in Maryland or Virginia. In 1846 the land from Virginia was given back to Virginia, so all current DC land was originally from Maryland. If both the U.S. Congress and the Maryland state legislature agreed, DC land (except for federal land) could be given back to Maryland with only a small federal area.
+## Treat District Residents as Maryland Voters for Federal Congressional Elections. Congress could give DC residents the right to vote as if they were part of Maryland for the Senate and House of Representatives (including the calculations for apportioning House seats).
+
+On a local level, the city is run by an elected Mayor and City Council. The school board has both elected and appointed members. However, Congress has plenary power over the district. It has the right to review and overrule laws created locally, and has often done so.
+
+DC residents pay all federal taxes, such as income tax, as well as local taxes. The Mayor and Council adopt a budget of local money with Congress reserving the right to make any changes. Because so much of the valuable property in the district is federally-owned and hence exempt from local property taxes, the city is frequently cash-strapped; public services in the city suffer as a result.
+
+==History==
+[[image:Karte_Washington_MKL1888.png|right|frame|[[1888]] [[Germany|German]] map of Washington, DC]]
+Washington was selected as the site of the national capital city after a sitdown dinner deal between [[Thomas Jefferson]] and [[Alexander Hamilton]]. Jefferson agreed to support Hamilton's banking and federal bond plans in exchange for the choice of a Southern locale for the capital. It was initially 100 [[square mile|mi&sup2;]] (260 [[square kilometer|km&sup2;]]).
+
+The signing of the [[Residence Bill]] on [[July 16]], [[1790]] established a site along the [[Potomac River]] as the District of Columbia (seat of government) of the United States. Land for the district was given to the federal government by the states of [[Virginia]] and [[Maryland]] and the city was named after [[George Washington]]. On [[February 27]], [[1801]] the district was placed under the jurisdiction of the [[United States Congress]]. The towns of [[Georgetown, Washington, D.C.|Georgetown]] and [[Alexandria, Virginia|Alexandria]] already existed at the time the district was founded; the remainder of the territory was subdivided into Washington City and Washington County (on the Maryland side of the Potomac) and [[Arlington County, Virginia#History|Alexandria County]] (on the Virginia side). In [[1871]], Georgetown, Washington City and [[Washington County, Washington, DC|Washington County]] were unified into Washington, DC.
+
+By an act of Congress, the area south of the [[Potomac River|Potomac]] (39 [[square mile|mi&sup2;]] or about 100 [[square kilometer|km&sup2;]]) was returned to Virginia on [[July 9]], [[1846]] and now is incorporated into [[Arlington County, Virginia|Arlington County]] and a part of the City of Alexandria.
+
+On [[August 24]], [[1814]], British forces burnt the capital during the most notable destructive raid of the [[War of 1812]]. British forces burned public buildings including the White House, the Capitol, the Arsenal, the Dock-Yard, Treasury, War office, and the bridge across the Potomac.
+
+President [[James Madison]] was forced to flee to Virginia and American morale was reduced to an all-time low. The expedition was carried out between August 19 and August 29, 1814, and was well organized and vigorously executed. On the 24th the American militia, who had collected at Bladensburg, Maryland, to protect the capital, fled almost before they were attacked.
+
+President [[Herbert Hoover]] ordered the [[United States Army]] on [[July 28]], [[1932]] to forcibly evict the "[[Bonus Army]]" of [[World War I]] veterans that gathered in Washington, DC to secure promised veteran's benefits early. U.S. troops dispersed the last of the "Bonus Army" the next day.
+
+The [[Twenty-third Amendment to the United States Constitution]] was ratified on [[March 29]], [[1961]] which allows residents of Washington, DC to vote for President (popular election) and have their votes count in the [[U.S. Electoral College|Electoral College]] the same as the least populous state, which currently is three (3).
+
+The first 4.6 miles (7.4 kilometers) of the [[Washington Metro]] [[metro|subway]] system opened on [[March 27]], [[1976]].
+
+[[Walter Washington]] became the first elected mayor of the District in [[1974]]. During his third term, Mayor [[Marion Barry]] was arrested for [[drug]] use in an [[FBI]] sting on [[January 18]], [[1990]]. He was acquitted of felony charges, but convicted on one misdemeanor count of [[cocaine]] possession for which he served a six-month jail term. On [[January 2]], [[1991]] [[Sharon Pratt Kelly]] (elected as '''Sharon Pratt Dixon''' but married later that year) was sworn in as mayor of Washington, DC becoming the first black woman to lead a city of that size and importance in the United States. After her term ended in [[1994]], Marion Barry was once again elected mayor for his fourth term. The current mayor, [[Anthony Williams]], a Yale educated lawyer, became mayor in [[1998]]. He was reelected in [[2002]]. See [[List of mayors of Washington, D.C.]]
+
+The Washington area was the target of at least one of the four hijacked planes in the [[September 11, 2001 attacks]]. One plane struck [[the Pentagon]] in Arlington County, killing 125 people in addition to the 64 aboard the plane, while another that was downed in a field in [[Pennsylvania]] is believed by many to have been intended to hit either the [[White House]] or the [[U.S. Capitol]].
+
+Shortly after September 11, Washington was once more subject to fear from an [[2001 anthrax attack|anthrax attack]], when what may have been a domestic [[terrorist]] sent [[anthrax]]-contaminated mail to numerous members of Congress. Thirty-one staff members were infected, and two [[U.S. Postal Service]] employees at a contaminated mail sorting facility at [[Brentwood, Washington, DC]], later died of pulmonary anthrax.
+
+During three weeks of October [[2002]], [[John Allen Muhammad]] and [[Lee Boyd Malvo]] perpetrated what became known as the [[Beltway Sniper]] attacks in Washington and across the Baltimore-Washington Metropolitan Area. Muhammed and Malvo killed ten people and critically injured three others with a high-powered rifle. The apparently random selection of victims (crossing racial, gender, and socioeconomic categories) caused a general panic in the Washington area and led schools to cancel all outdoor activities. Muhammed and Malvo were arrested on [[October 24]] at a highway rest stop. In March 2004, Muhammad was sentenced to death and Malvo to life imprisonment for the attacks.
+
+In November of [[2003]], the toxin [[ricin]] was found in the mailroom of the White House, and in February of [[2004]], in the mailroom of U.S. Senate Majority Leader [[Bill Frist]]. As with the earlier anthrax attacks, no arrests have been made.
+
+Partly in response to these events from the past few years, the Washington area has taken many steps to increase security. Screening devices for biological agents, [[metal detector]]s, and vehicle barriers are now much more commonplace at office buildings as well as government buildings. After the [[March 11, 2004 Madrid attacks|2004 Madrid train bombings]], local authorities have decided to test explosives detectors on the vulnerable [[Washington Metro]] subway system. False alarms due to suspicious chemical or powder substances or suspected explosives have led to fairly frequent evacuations of buildings, Metro stations, and local post offices. Vehicle inspections at several roadblocks set up around the [[U.S. Capitol]] building were introduced in July, 2004.
+
+== Geography ==
+[[Image:Dc satellite image.jpg|thumb|right|400px|Color-enhanced [[United States Geological Survey|USGS]] satellite image of Washington, DC, taken April 26, 2002. The [[Potomac River]] and its eastern branch, the [[Anacostia River]], are visible. [[Virginia]] lies across the Potomac from Washington, while [[Maryland]] surrounds it on all other sides. The black "crosshairs" in the image mark the quadrant divisions of Washington, with the [[U.S. Capitol]] at the center of the dividing lines. To the west of the Capitol extends the [[National Mall]], visible as a slight green band in the image. [[The Pentagon]] is also visible in Virginia, near the Potomac.]]
+Washington is located at 38&deg;54'49" North, 77&deg;0'48" West (38.913611, -77.013222)[[Geographic references|<sup>1</sup>]].
+
+According to the [[United States Census Bureau]], the city has a total area of 177.0 [[square kilometer|km&sup2;]] (68.3 [[square mile|mi&sup2;]]). 159.0 km&sup2; (61.4 mi&sup2;) of it is land and 18.0 km&sup2; (6.9 mi&sup2;) of it is water. The total area is 10.16% water.
+
+Washington is surrounded by the [[U.S. State|state]]s of [[Virginia]] (on its southwest side, and a small part of its northwest one) and [[Maryland]] (on its southeast and northeast sides, and most of its northwest one); it interrupts those states' common border, which is the Potomac River both upstream and downstream from the District. The city contains the historic '''federal city''', the territory of which was formerly part of those two adjacent states before they respectively ceded it for the national capital. The land ceded from Virginia was returned by [[United States Congress|Congress]] in [[1847]], so what remains of the modern District was all once part of Maryland.
+
+''See also'' [[District of Columbia (geography)]].
+
+===City layout===
+
+The original street layout was designed by [[Pierre Charles L'Enfant]] at the time of the city's founding. Washington is divided into four quadrants, directly along the four compass directions: [[Washington DC (northwest)|Northwest]], [[Washington DC (southwest)|Southwest]], [[Washington DC (northeast)|Northeast]], and [[Washington DC (southeast)|Southeast]]. Every street name has appended to it the abbreviation of the quadrant that it is in&mdash;e.g., Connecticut Ave., NW, New York Ave., NE. A street's quadrant is necessary to include in postal addresses, especially because much of the city's street layout repeats within each quadrant. The north-to-south numbered streets in Washington and count upwards from east to west in NW/SW (1st St NW, 2nd St NW, 3rd St NW, etc.); these streets repeat in NE/SE, counting upwards from west to the east. The east-to-west lettered streets (A St, B St, etc.) "count" upwards from south to north in NW/NE, and likewise repeat in the opposite direction in SW/SE. Street numbers count upwards traveling outward from the dividing lines of the quadrants.
+
+The center of the north/south and east/west dividing lines is the [[U.S. Capitol]], which is offset from the physical center of Washington's diamond shape making the quadrants unequal in size. Additionally, much of what was SW is now Arlington County, Virginia (or the Potomac River), making it by far the smallest quadrant; NW is the largest.
+
+L'Enfant's plan also includes many diagonal avenues named after the states, such as [[Pennsylvania Avenue, Washington D.C.|Pennsylvania Avenue]] which connects the Capitol and the White House.
+
+===Neighborhoods===
+
+Washington includes many distinct and historic neighborhoods:
+{| width="100%"
+|width="40%" align=left valign=top|
+* [[Adams Morgan]]
+* [[Anacostia]]
+* [[Brentwood, Washington, DC|Brentwood]]
+* [[Brookland, Washington, DC|Brookland]]
+* [[Burleith]]
+* [[Capitol Hill, Washington, DC|Capitol Hill]]
+** ''[[Eastern Market, Washington, DC|Eastern Market]]''
+** ''[[Barracks Row, Washington, DC|Barracks Row]]''
+* [[Chevy Chase, Washington, DC|Chevy Chase]]
+* [[Chinatown, Washington, DC|Chinatown]]
+* [[Cleveland Park]]
+* [[Columbia Heights, Washington, DC|Columbia Heights]]
+* [[Dupont Circle]]
+* [[Foggy Bottom]]
+|width="40%" align=left valign=top|
+* [[Friendship Heights]]
+* [[Georgetown, Washington, DC|Georgetown]]
+* [[Hillcrest, Washington, DC|Hillcrest]]
+* [[Le Droit Park]]
+* [[Logan Circle]]
+* [[Mount Pleasant, Washington, DC|Mount Pleasant]]
+* [[Washington DC Navy Yard|Navy Yard]]
+* [[Penn Quarter, Washington, DC|Penn Quarter]]
+* [[Petworth, Washington, DC|Petworth]]
+* [[Shepherd Park]]
+* [[Shaw, Washington, DC|Shaw]]
+* [[Swampoodle]]
+* [[Takoma, Washington, DC|Takoma]]
+* [[Tenleytown]]
+* [[Trinidad, Washington, DC|Trinidad]]
+|}
+''([http://www.h-net.org/~dclist/neighborhoods.html External link to DC neighborhood websites])''
+
+== Demographics ==
+As of the [[census]] of [[2000]], there are 572,059 people, 248,338 households, and 114,235 families residing in the city. The [[population density]] is 3,597.3/km&sup2; (9,316.4/mi&sup2;). There are 274,845 housing units at an average density of 1,728.3/km&sup2; (4,476.1/mi&sup2;). The racial makeup of the city is 30.78% [[White (U.S. Census)|White]], 60.01% [[African American (U.S. Census)|African American]], 0.30% [[Native American (U.S. Census)|Native American]], 2.66% [[Asian (U.S. Census)|Asian]], 0.06% [[Pacific Islander (U.S. Census)|Pacific Islander]], 3.84% from other races, and 2.35% from two or more races. 7.86% of the population are [[Hispanic (U.S. Census)|Hispanic]] or [[Latino (U.S. Census)|Latino]] of any race.
+
+There are 248,338 households out of which 19.8% have children under the age of 18 living with them, 22.8% are married couples living together, 18.9% have a female householder with no husband present, and 54.0% are non-families. 43.8% of all households are made up of individuals and 10.0% have someone living alone who is 65 years of age or older. The average household size is 2.16 and the average family size is 3.07.
+
+In the city the population is spread out with 20.1% under the age of 18, 12.7% from 18 to 24, 33.1% from 25 to 44, 21.9% from 45 to 64, and 12.2% who are 65 years of age or older. The median age is 35 years. For every 100 females there are 89.0 males. For every 100 females age 18 and over, there are 86.1 males.
+
+The median income for a household in the city is $40,127, and the median income for a family is $46,283. Males have a median income of $40,513 versus $36,361 for females. The per capita income for the city is $28,659. 20.2% of the population and 16.7% of families are below the poverty line. Out of the total people living in poverty, 31.1% are under the age of 18 and 16.4% are 65 or older.
+
+== Economy ==
+Several major companies are based in Washington, including the [[Carlyle Group]] and Marriott International, Inc.
+
+[[America Online]] and [[Orbital Sciences Corporation]] are based in nearby [[Dulles, Virginia|Dulles]], [[Virginia]]. [[MCI]] is based in nearby [[Ashburn, Virginia]]. [[Nextel Communications|Nextel]] and [[Unisys]] are based in [[Reston, Virginia]]. [[US Airways]] is based in [[Arlington County, Virginia]]. [[Colgan Air]] is based in nearby [[Manassas, Virginia]]. [[Lockheed Martin]] is based in nearby [[Bethesda, Maryland|Bethesda]], [[Maryland]]. [[Alhurra]] is based in [[Springfield, Virginia]]. [[Independence Air]] is based at nearby [[Dulles International Airport]].
+
+The American genomics industry is largely centered around the Maryland suburbs of Washington. Prominent players are [[Celera]], [[The Institute for Genomic Research]] (also known as "TIGR"), and [[Human Genome Sciences]] (all of which are in the city of [[Rockville, Maryland]]).
+
+== Cultural features ==
+<div style="float: right; margin: 0 0 1em 1em; text-align: center;"><br>[[image:jfmhelicoptor.jpg]]<br><small>''[[Jefferson Memorial]]''</small></div>
+===Local media===
+
+''[[The Washington Post]]'' is the oldest and most read daily [[newspaper]] in Washington. The ''Post'' is also one of the most reputable daily newspapers in the U.S. and is highly influential in its political reporting, particularly after the role of its reporters in cracking the [[Watergate]] scandal. The daily ''[[Washington Times]]'' and the free weekly ''[[Washington City Paper]]'' also have substantial readership in the District. The weekly ''[[Washington Blade]]'' focuses on [[gay]] issues.
+
+Washington is served by the following local broadcast [[television]] stations:
+
+* [[WBDC]]&mdash;Channel 50, a [[The WB Television Network|WB]] affiliate
+* [[WDCA]]&mdash;Channel 20, a [[UPN]] affiliate
+* [[WRC (television)|WRC]]&mdash;Channel 4, an [[National Broadcasting Company|NBC]] affiliate
+* [[WETA]]&mdash;Channel 26, a [[Public Broadcasting Service|PBS]] affiliate
+* [[WJLA]]&mdash;Channel 7, an [[American Broadcasting Company|ABC]] affiliate
+* [[WTTG]]&mdash;Channel 5, a [[Fox Network|FOX]] affiliate
+* [[WUSA]]&mdash;Channel 9, a [[CBS]] affiliate
+
+=== Landmarks and museums ===
+
+Washington is the home of numerous national landmarks and is a popular tourist destination. Landmarks include:
+<table width=100%><tr><td width=50% align=left valign=top>
+*[[Blair House]]
+*[[Catholic University of America]], whose grounds house the [[Basilica of the National Shrine of the Immaculate Conception]]
+*[[City Museum of Washington, DC|City Museum]]
+*[[Corcoran Museum of Art]]
+*[[Franklin Delano Roosevelt Memorial]]
+*[[Jefferson Memorial]]
+*[[John F. Kennedy Center for the Performing Arts]]
+*[[International Spy Museum]]
+*[[Korean War Veterans Memorial]]
+*[[Library of Congress]]
+*[[Lincoln Memorial]]
+*[[The Mall (Washington, DC)|National Mall]]
+*[[National Gallery of Art]]
+*[[National World War II Memorial]]
+*[[Old Post Office Building (Washington)|Old Post Office Building]]
+*[[The Phillips Collection]]
+</td><td width=50% align=left valign=top>
+*[[Smithsonian Institution]], a collection of musuems including:
+**[[Anacostia Museum]]
+**[[Arthur M. Sackler Gallery]]
+**[[Hirshhorn Museum]]
+**[[National Air and Space Museum]]
+**[[National Museum of American Art]]
+**[[National Museum of American History]]
+**[[National Museum of the American Indian]]
+**[[National Museum of Natural History]]
+**[[National Portrait Gallery, Washington, DC|National Portrait Gallery]]
+**[[National Postal Museum]]
+**[[National Zoological Park|National Zoo]]
+*[[United States Capitol]]
+*[[United States Holocaust Memorial Museum]]
+*[[Vietnam Veterans Memorial]]
+*[[Washington Monument]]
+*[[Washington National Cathedral]]
+*[[White House]]</td></tr></table>
+
+=== Colleges and universities ===
+<table width=100%><tr><td width=50% align=left valign=top>
+*[[American University]]
+*[[The Catholic University of America]]
+*[[Corcoran College of Art and Design]]
+*[[Gallaudet University]]
+*[[George Washington University]]
+*[[Georgetown University]]
+</td><td width=50% align=left valign=top>
+*[[Howard University]]
+*[[National Defense University]]
+*[[Southeastern University]]
+*[[Strayer University]]
+*[[Trinity University (Washington, DC)|Trinity University]]
+*[[University of the District of Columbia]]</td></tr></table>
+
+=== High Schools ===
+
+*[[Gonzaga College High School]]
+
+=== Sports and entertainment ===
+
+Washington is home to several professional sports teams: the [[Major League Soccer|MLS]] [[D.C. United]], the [[National Hockey League|NHL]] [[Washington Capitals]], the [[Women's National Basketball Association|WNBA]] [[Washington Mystics]], the [[National Basketball Association|NBA]] [[Washington Wizards]], and a as-of-yet-unnamed [[Washington D.C. Major League Baseball Franchise]] (the former [[Montreal Expos]], who will start play in [[2005]]). It also hosts the annual Legg Masson Tennis Classic [[tennis]] tournament.
+
+Other professional and semi-professional teams based in DC include the [[Australian Rules Football|USAFL]] Baltimore Washington Eagles, the [[National Women's Football Association|NWFA]] D.C. Divas, the Minor League Football DC Explosion, and the [[cricket|Washington Cricket League]]. It was also home to the [[Women's United Soccer Association|WUSA]] [[Washington Freedom]], and, during the [[2000]]&ndash;[[2002]] [[National Lacrosse League|NLL]] seasons, the [[Washington Power]] was based in the city.
+
+The [[National Football League|NFL]] [[Washington Redskins]] formerly played at R.F.K. Stadium in the District, but are now based at FedEx Field in [[Landover, Maryland]].
+
+There were two [[Major League Baseball]] teams named the [[Washington Senators]] in the early and mid-[[20th century|20<sup>th</sup> century]], which left to become respectively the [[Minnesota Twins]] and the [[Texas Rangers]]. In the [[Washington_Senators#Premodern_baseball_in_Washington|premodern era of baseball]], the town was home to teams called the Washington Nationals, Washington Statesmen, and Washington Senators on and off from the [[1870s]] to the turn of the century. It was also home to several [[Negro League baseball|Negro League]] teams, including the [[Homestead Grays]], Washington Black Senators, Washington Elite Giants, Washington Pilots, and Washington Potomacs. On [[September 29]], [[2004]] MLB announced plans to relocate the [[Montreal Expos]] to Washington, pending certain conditions including approval by the City Council of a stadium deal. The market is also home to many fans of the [[Baltimore Orioles]] of [[Baltimore, Maryland]], whose owner initially opposed the move of the Expos to DC.
+
+The [[MCI Center]] in [[Chinatown]], home to the Capitals, Mystics, Wizards, and the Georgetown Hoyas, is also a major venue for concerts, [[WWE]] [[professional wrestling]], and other events.
+
+The [[Kennedy Center for the Performing Arts]] hosts the [[National Symphony Orchestra]], the [[Washington Opera]], the [[Washington Ballet]], and other musical and stage performances. Notable local music clubs include Madam's Organ Blues Bar in Adams Morgan, and [[the Black Cat]], [[the 9:30 Club]], and the historic [[Bohemian Caverns]] jazz club, all in the U Street NW area.
+
+Washington was an important center in the genesis of [[punk rock]] in the United States. Punk bands of note from Washington include [[Fugazi]], [[Bad Brains]], and [[Minor Threat]]. Native Washingtonians continue to support punk bands, long after the punk movement's peak in popularity. The region also has a storied [[indie rock]] history and was home to [[TeenBeat Records|TeenBeat]] and [[Simple Machines Records|Simple Machines]], among other indie record labels.
+
+== Transportation ==
+
+The [[I-495]] Beltway surrounds the Washington area. The [[I-270]] spur connects I-495 with [[I-70]]. The [[I-395]] spur breaks off of [[I-95]] at the Beltway to connect northern Virginia with downtown Washington. [[I-66]] connects to [[I-495]] and provides access to the western edges of northern Virginia, and continues out to the west.
+
+The Washington area is serviced by the [[Washington Metro]] public transportation system, which operates public buses and the region's subway system.
+
+=== Airports ===
+Washington is located in proximity to three airports: [[Washington Dulles International Airport]] (IAD) between [[Dulles, Virginia]] and [[Chantilly, Virginia]]; [[Ronald Reagan Washington National Airport]] (DCA) in Arlington County; and [[Baltimore/Washington International Airport]] (BWI) near [[Baltimore, Maryland]].
+
+[[Washington Dulles International Airport|Dulles International]] is used for most international travel to and from DC, and is the largest domestic [[low-cost carrier|low-cost]] hub in the US. [[Baltimore/Washington International Airport|Baltimore/Washington]] is used more for international and domestic service serving the Baltimore PMSA.
+
+==External links==
+===General information and activity guides===
+*[http://www.dc.gov/ Washington, DC official site]
+*[http://www.washington.org/ official tourism site]
+*[http://www.culturaltourismdc.org/ Cultural Tourism in DC]
+*[http://www.citymuseumdc.org/ City Museum of Washington, DC]
+*[http://www.our-dc.com/ OurDC.com - A website featuring oft-overlooked DC cultural offerings]
+*[http://www.greaterwashington.org/ Greater Washington regional economic initiative]
+
+* Major DC entertainment guides: [http://www.washingtonpost.com/wp-dyn/artsandliving/entertainmentguide/?nav=globetop The Washington Post] | [http://www.washingtoncitypaper.com/arts.shtml Washington City Paper]
+
+===DC representation debate===
+*[http://www.dcvote.org/ DC Vote] An organization working for District representation in Congress
+*[http://www.washingtonmd.org Committee for the Capital City] An organization supporting retrocession of DC to Maryland
+*[http://www.dwheeler.com/essays/dc-in-maryland.html "Treat Washington, DC as Part of Maryland for Congressional Elections"] argues for this particular approach for DC representation in Congress.
+
+==Sources==
+
+* http://flagspot.net, http://flagspot.net/flags/us-dc.html - Source for flag image - Flag image made by Mark Sensen
+
+[[da:Washington DC]]
+[[de:Washington, D.C.]]
+[[es:Washington, DC]]
+[[eo:Va&#349;ingtono]]
+[[fr:Washington, DC]]
+[[ko:&#50892;&#49905;&#53556; D.C.]]
+[[it:Washington DC]]
+[[nl:Washington DC]]
+[[ja:&#12527;&#12471;&#12531;&#12488;&#12531;D.C.]]
+[[pl:Dystrykt Kolumbii]]
+[[sv:Washington D.C.]]
+[[zh-cn:&#21326;&#30427;&#39039;&#24066;]]
+
+{{United_States}}
+[[Category:Cities in the United States]]
+[[Category:Political divisions of the United States]]
+[[Category:Washington, DC| ]] \ No newline at end of file
diff --git a/vendor/wikimedia/utfnormal/scripts/testdata/young.txt b/vendor/wikimedia/utfnormal/scripts/testdata/young.txt
new file mode 100644
index 00000000..7a907610
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/scripts/testdata/young.txt
@@ -0,0 +1,274 @@
+{{다른 뜻}}
+{{POV}}
+{{밴드 정보
+|밴드명 = 이수영
+|그림 = LeeSooYoung.jpg
+|국가 = 대한민국
+|활동시기 = [[1999년]] - 현재
+|장르 = [[발라드]]
+|라인업 = 솔로
+}}
+'''이수영''' (李秀英, [[1979년]] [[4월 12일]] - )은 [[대한민국]]의 [[가수]]이다. 본명은 '''이지연'''(李知姸)이다.
+종교는 [[개신교]]이다.
+가족관계로는 여동생과 남동생이 각각 한 명씩 있다.
+부모는 [[교통사고]]로 사망하였다.
+
+[[1999년]] 〈I Believe〉로 데뷔했다. 대표곡으로는〈라라라〉, 〈그리고 사랑해〉,〈덩그러니〉,〈휠릴리〉 등이 있다. 가장 최근 앨범은 정규 7집 《[[Grace]]》로서 2006년 1월 21일 발매되었다. 《Grace》의 [[리팩키지 앨범]]인 《Speical Grace》가 발매되었다.(리팩키지 앨범은 정규 혹은 비정규 음반으로 간주하지 않는다.) 정규 8집 앨범은 [[2006년]] [[가을]]에 발매가 될 것으로 예정되어 있었으나 소속사와 불화로 무기한 연기된 상태다.
+== 데뷔 전 ==
+
+=== 가정사 ===
+[[서울]]에서 태어났고 그녀가 9살 되던 해에 아버지가 [[교통사고]]로 사망하였다. 어머니는 세 자녀를 키우기 위해 식당 일,우유 배달 등을 하며 생계를 꾸려나갔다. 자연스레 집안일과 어린 두 동생을 돌보는 것은 이수영의 몫이되어 그녀는 일찍이 나이에 비해 정신적으로 많이 성숙했다.
+
+어릴 적부터 가수가 꿈이었던 이수영은 16살 때 MBC 라디오 프로그램인 [[별이 빛나는 밤에]]의 장기자랑 코너에 나가 장원을 수상하였고, 당시 별밤 진행자였던 [[이문세]]는 그녀의 음색에 대해 칭찬을 아끼지 않았다. 그 당시에, 별밤 장기자랑 코너의 장원은 주,월,기 별로 있었는데 아쉽게도 이수영이 이사를 가는 바람에 기장원선발에 참가하지는 못했다.
+
+18살이 되던 해에 이수영은 [[머라이어 캐리]] 등의 가수들의 노래를 불러서 만든 데모 테잎을 [[이가 엔터테인먼트]]에 보냈고 얼마 후 연락이 왔으며 그 때부터 이수영은 가수 트레이닝을 하기 시작했다.
+
+1년 뒤 그녀는 [[가스펠]]이 어떤 음악인지 궁금해서 방문한 교회에서 처음으로 [[개신교]]를 접하게 되었다. 깊은 믿음을 가지게 된 그녀는 학업을 뒷전으로 미루고 교내 친구들을 상대로 전도활동에 전념하였다. 이러한 믿음이 그녀를 지탱하게 해주는 원동력이 되었다고 후에 고백한 바 있다.
+
+얼마 후 이수영은 학교에서 종례를 마친 후 한 통의 다급한 전화를 받게 되었는 데, 그녀의 어머니가 교통사고를 당했다는 것이었다. 이수영의 어머니는 병원으로 후송 된 직후 사망하였다.
+
+이듬 해, 이수영은 데뷔 앨범인 《[[I Believe]]》를 발매하고 [[가수]]로 데뷔하였다.
+
+=== 트레이닝 ===
+[[이가 엔터테인먼트]]의 사장 [[이도형]]은 이수영을 어느 한 분야로 집중 트레이닝 시키지 않았다.
+이수영은 발라드 가수지만 트레이닝 시절에는 활동할 음악 분야를 정해놓지 않았기에 그녀가 과장해 말한 것인지는 모르나 “10시간 보컬 연습 10시간 안무 연습 4시간 취침”이었다고 당시를 회상했다.
+
+트레이닝은 매우 혹독했다. 힘들 때마다 이수영은 어머니와 어린 두 동생들을 생각하며 버텼다.(그러나 어머니는 이수영이 데뷔하기 전 교통사고로 사망했다.) 트레이닝이 막바지에 이를 무렵 이도형은 이수영의 음색이 댄스 가수의 그것이 아니라 판단하여 발라드 가수로 데뷔시키기로 결정했다. 이도형의 스파르타식 트레이닝과 관리는 데뷔 후에도 계속 되었으며 2집 활동 때 절정에 달했다. 혹독한 트레이닝으로 이수영은 2집활동 당시 38kg이란 몸무게를 기록하기도 했다.
+
+== 앨범 활동 ==
+
+=== 1집 ===
+데뷔 앨범인 1집 《[[I Believe]]》은 [[1999년]] [[11월 16일]] 발매되었다. 이수영은 오리엔탈 발라드라는 독특한 분위기의 곡인 첫 번째 싱글 〈I Believe〉로 활동하여 큰 사랑을 받았으며 신인답지 않게 각종 차트의 상위권을 차지했다. 소속사의 관리는 매우 혹독해서 공연이 끝나면 곧바로 회사로 돌아와 녹화된 화면을 보며 몇 시간씩 모니터링을 하며 잘못된 점을 지적했다.
+
+〈I Believe〉의 성공에 이어 두 번째 싱글 〈Good bye my love〉로 활동했으나 그리 큰 반응을 이끌어내지는 못했다. 1집은 그녀가 가요계에 등장한 데뷔앨범으로 중요한 의미를 갖는다.
+
+==== 거짓 프로필 ====
+데뷔 당시 소속사가 이수영에 대해 속인 것이 세가지가 있었으니 그것은 바로 키,나이 그리고 가족사항이었다. 프로필에는 키가 165cm로 기록되어있지만 실제 키는 161.8cm라고 이수영은 감성시대 진행 중 고백했다. 또한 나이를 [[1980년]]생으로 속였는데 이것은 이수영이 [[1979년]]생 연예인들의 친목클럽인 '79클럽'에 가입함에 따라 거짓이었음이 드러났다.
+
+또한 양 부모님이 모두 살아계신다고 하였으나 이수영을 개인적으로 아는 지인들의 글이 PC통신과 입소문을 통해 퍼지면서 소녀가장이라는 소문이 돌게 되었다. 얼마 후 이 사실을 시인했는데 소속사는 이것을 홍보 방법의 하나로 이용하여 동정심 마케팅 전략을 펼쳤다. 이후 이수영은 몇 년간 '소녀가장 가수'라는 꼬리표를 달게 되었다. (사실 이수영이 데뷔했을 때 그녀는 성인이었으므로 소녀가장이라 칭한 것은 잘못된 것이다.) 지금도 일부 사람들은 그녀를 어린 두 동생들을 돌보며 살아온 소녀가장 가수로 기억하며 동정심을 표하기도 한다. 당시 소속사의 그런 홍보방법에 대해 이수영은 "부모님을 두 번 죽이는 기분"이었다고 회상했다.
+
+=== 2집 ===
+2집 《[[Never again]]》은 [[2001년]] [[2월 2일]] 발매되었다. 첫 번째 싱글 〈Never again〉은 웅장한 [[오케스트라]] [[세션]]이 동원된 곡으로 좋은 반응을 이끌어냈다. 두 번째 싱글〈스치듯 안녕〉활동을 시작하면서 이수영은 긴 생머리를 단발로 바꾸고 몇몇 예능 프로그램에 출연하기도 하였다. 〈스치듯 안녕〉은 〈Never again〉보다 오히려 더 큰 사랑을 받았다.
+
+2집 활동은 그야말로 이수영에게는 암흑기와 같았다. 아직은 신인인 상태라 소속사의 압박이 매우 심했다. 이러한 것은 두 번째 싱글 〈스치듯 안녕〉활동 시 절정에 달했는데 이수영의 일거수일투족이 감시당했고 심지어 매일 밤마다 이수영은 그녀가 집에 있는지 여부를 확인하는 소속사의 전화를 받아야 했으며 방송국 대기실에서 그 누구와도 말을 하지 못하게 했으며 다이어트를 명목으로 식사량도 강제적으로 제한했다. 이러한 사실을 안 친구 [[이효리]]는 이수영을 밤에 몰래 빼내어 영화를 관람하거나 식사를 대접하는 등의 도움을 줬다고 후에 라디오 프로그램을 통해 고백했다.
+
+이수영은 〈스치듯 안녕〉활동 당시 갑작스럽게 쓰러지고 말았는데 소속사는 그런 이수영을 병원에 몇 일 입원시켜 치료를 받게 한 뒤 퇴원과 동시에 곧바로 방송국으로 보냈다. 이수영은 아무리 아파도 무대에 서야했고 그로 인해 〈스치듯 안녕〉은 대부분 립싱크 무대를 선보이게 되었다. 이수영은 소속사의 압박과 엄격한 관리로 인해 점점 더 건강이 나빠졌고 이내 2집 활동을 마무리 하게 되었다.
+
+
+==== 단발 머리 ====
+〈스치듯 안녕〉활동 시 이미지 변신의 일환으로 헤어스타일에 변화를 주려고 미용실을 방문한 이수영은 긴 생머리를 약간만 자르려고 했으나 그 날따라 기분이 좋지 않았던 헤어디자이너는 홧김에 머리카락을 너무 많이 잘라버렸다. 결국 이수영은 단발이 되었고 이것은 의도하지 않았던 결과였기에 얼마 간 헤어스타일에 알맞는 분위기연출을 구상하느라 애를 먹게 되었다. 사실 그것은 또다른 도약의 길을 열어논 셈이 된것이었다.
+
+=== 3집 ===
+3집 《[[Made in winter]]》는 [[2001년]] [[12월]]에 발매되었다. 기존 앨범과 달리 현악기 세션을 최대한 자제한 이 앨범의 첫 번째 싱글 〈그리고 사랑해〉는 〈라라라〉와 함께 팬들에게 이수영 최고의 명곡으로 꼽히고 있다. 3집 활동의 주된 변화는 예능 프로그램출연을 통한 홍보였다. 많은 이들이 노래는 아는데 그녀의 얼굴은 모르는 상황이었다. 각종 예능 프로그램에 출연하여 연변 사투리 개인기를 선보여 많은 사랑을 받았다. 후에 이 연변 사투리는 실제 연변에 거주하는 일부 사람들에게 자신들을 모욕한다며 반발을 사기도 하였다. 이수영은 MBC 예능 프로그램인 [[일요일 일요일 밤에]]의 '게릴라 콘서트' 코너에 출연해 자신의 이미지를 대중에게 강하게 인식시키는데 성공한다.
+
+홍콩의 유명 영화배우인 [[청룽]]과 함께 청룽의 앨범에 수록된 듀엣곡인 〈몽상적 천제〉를 녹음하며 동남아시아 프로모션의 모습도 보였으나 보도자료에서와 달리 프로모션 행사 이후 동남아시아에 더 이상 방문하지 않았고 활동 또한 전무한 것으로 보아 이것은 소속사의 반짝 홍보였다는 의견이 지배적이다.
+
+이수영은 일본 유명 비디오게임인 《[[파이널 판타지]]》 열 번째 편의 인터네셔널 버전의 OST를 한국어로 번안하여 녹음했다. 처음에는 단순히 OST참여에 지나지 않았으나 〈얼마나 좋을까〉는 게임유저들에게 이수영이란 존재를 알림과 동시에 수많은 팬을 끌어모으는데 성공했다. 두 번째 싱글인 〈차라리〉로 활동하던 중 MBC 라디오프로그램 [[감성시대]]를 진행하게 되었다. 감성시대 진행과 〈그리고 사랑해〉의 성공으로 수 많은 팬들이 생겼다.〈차라리〉는 그리 특징적인 반응을 이끌어내지는 못했고 [[2002년]] [[6월]] 월드컵 개막과 동시에 3집 활동을 마무리 지었다.
+
+=== 라디오 DJ ===
+[[2002년]] [[4월]] MBC는 정규 봄 개편을 단행했다. 봄 개편과 더불어 이수영은 MBC 표준 FM에서 오전 0시에 진행되는 [[감성시대]] DJ를 맡게된다. 감성시대는 이수영 특유의 유머 감각과 인간미가 드러나 많은 청취자들에게 사랑을 받았으며 아직도 역대 최고의 라디오 프로그램이 무엇이냐는 질문에 많은 사람들이 이수영의 감성시대라고 답한다.
+
+심야 프로그램답게 조용히 진행되던 감성시대는 시간이 흐를수록 밝고 왁자지껄한 프로그램이 되었다. 이수영은 그 동안 대중에게 노출하지 않았던 자신의 유머 감각을 선보였으며 마음 속에 담아두고 있었던 많은 이야기를 했다. 이 두가지 모두 소속사에서는 바라지 않는 부분이었다. 많은 연예인들을 게스트로 만나 이야기를 나누어 이로 인해 인맥을 많이 넓힐 수 있었고 기존에 그녀에게 박혀있던 조용하고 차분 가수 이미지를 대중에게는 물론 타 연예인들에게도 털어내버렸다.
+
+[[2003년]] [[3월]]이 되자 돌연 DJ 자리에서 내려오게 되었다.
+겉으로는 일본 진출을 위해서라고 했지만 실은 소속사의 통제가 아니냐는 의혹이 있었으며 실제로 이수영의 일본 진출은 1년 3개월 후에 이루어졌다.
+
+이로써 이수영은 [[2002년]] [[4월 1일]]부터 [[2003년]] [[3월 10일]]까지 진행하던 감성시대 DJ를
+그룹 UN의 [[최정원]]에게 물려주고 DJ 활동을 마무리 지었다.
+
+=== 4집 ===
+이수영 최고의 상업적 성공과 명성을 안겨준 4집 《[[My stay in Sendai]]》는 [[감성시대]]를 진행 중이던 [[2002년]] [[9월 16일]] 발매되었다.
+이수영은 앨범 활동과 [[DJ]]활동을 병행하였는데 이 점은 앨범 홍보에 상당한 도움이 되었다. 대중들에게 얼굴이 알려지기 시작한 이수영은 4집에서 엄청난 인기를 얻게 되었다.
+
+첫 번째 싱글인 〈라라라〉는 곡의 변화가 크게 없는 이지 리스닝곡으로 이수영의 음색이 매우 두드러지게 표현되었다.〈라라라〉는 공중파 가요 프로그램 1위라는 숙원을 풀게 해주었다. SBS 가요프로그램인 [[인기 가요]]에서 1위를 수상한 이수영은 눈물을 흘리며 말을 잇지 못해 사회자들을 당황케 하였다. 두 번째 싱글인 〈빚〉도 〈라라라〉에 못지않는 인기를 구가하며 가요 프로그램 1위는 물론이고 각종 차트에서 상위권을 기록하였다. 이 여세를 몰아 [[2003년]] [[1월]] 4.5집 스페셜 앨범인 《[[Sweet holiday in Lombok]]》을 발매하여 싱글로는 일본 밴드 [[Zard]]의 곡인 〈Good day〉를 리메이크한〈굿바이〉를 앞세워 노래 세곡을 연이어 히트시켰다.
+
+4집은 그녀의 역대 앨범 중 가장 많이 팔렸고 폭발적인 인기를 얻은 앨범이다. 4집 활동으로 인해 이수영은 최고의 한해를 보냈으며 그 해 연말 사싱식프로그램에 첫 출연하게 되었다. 공식 활동 마무리 후 음악공부를 위해 외국 유학을 간다는 소문이 돌았고 소속사에서 보도자료를 배포하기도 했으나 이수영이 해외유학을 거절함에 따라 무산되었다.
+
+=== 5집 ===
+5집 《[[This time]]》은 [[2003년]] [[8월]]에 발매되었다. 그녀의 새 앨범에는 많은 이들의 이목이 집중되었다. 앨범은 발매되자마자 무서운 속도와 양의 판매고를 올렸다. 첫 번째 싱글 〈덩그러니〉는 공중파,케이블 TV프로그램,라디오 프로그램 가릴것 없이 각종 차트의 1위를 휩쓸었다. 이 인기는 두 번째 싱글 〈여전히 입술을 깨물죠〉로 그대로 이어져 가히 [[2003년]] 가요계를 이수영의 해라 해도 과언이 아니었다.
+
+그러나 한가지 문제가 발생했는데 바로 불안해진 라이브 실력 논란이었다. 전체적으로 떨리는 음정과 〈여전히 입술을 깨물죠〉활동 시 보여주었던 고음부분의 저음처리는 노래 연습 부족이 아니냐는 비난여론을 형성케 하였으며 라이브 잘하는 가수라는 이수영의 이미지에 타격을 입혔다. 라이브 실력 논란은 후에 발매된 정규 6집 앨범에서도 계속되었다.
+
+그 해 연말 이수영은 MBC 10대 가수가요제 최고인기가수상을 수상하는 영광을 안았다. 발표 당시 이수영은 본인이 수상했다는 사실에 엄청난 충격을 받아 대성통곡을 해서 앵콜 무대에서 마이크에서 흘러 나온 소리는 울음소리 뿐이었다. 시상식 직후 이수영은 병원에 입원하여 휴식을 취했다. 5집은 이수영을 대한민국 최고의 가수로 자리매김하게 만든 의미있는 앨범이었다.
+
+==== 과도한 보도자료 ====
+5집 활동 당시 소속사인 [[이가 엔터테인먼트]]는 너무나도 황당한 보도자료들을 내보내며 앨범 홍보에 열을 올렸는데 이것으로 인해 많은 사람들이 아직까지도 이수영에 대해 오해하고 있는 부분이 생겼다. 대표적인 것인 바로 시력 상실과 청력 상실 보도자료였다. 시력 상실과 청력 상실 보도자료는 모두 소속사에서 내보낸 홍보용 보도자료로써 이수영이 시력을 잃어가고 있으며 한 쪽 귀가 들리지 않는다는 내용이었다. 이 보도자료로 인해 몇몇 사람들은 가수가 노래를 부를 때 자신의 목소리를 듣기 위해 사용하는 이어폰이 보청기라 주장하는 웃지 못할 헤프닝도 일어났다.
+
+소속사 입장에서는 단순히 대중들에게 이수영을 알리고 약간의 동정심을 얻으려 했겠지만 이 일의 여파는 매우 큰 것이어서 3년이 지난 지금도 이수영 관련 소식을 접할 때 마다 "귀도 안들리는데.. 화이팅! 힘내세요!" 라는 식의 의견을 적어놓는 경우를 종종 볼 수 있다.
+
+또한 이수영의 밴에 도둑이 들어 속옷과 노출이 심한 옷을 입은 영상이 담긴 비디오 테잎을 훔쳐갔다는 매우 자극적인 보도자료를 배포하기도 하였다. 이가 엔터테인먼트의 과도한 보도자료 홍보방식은 비단 이수영 뿐만 아니라 같은 소속사 소속의 연예인인 [[한지혜]],[[심플리 선데이]] 등의 홍보에도 이용되었다.
+
+=== 리메이크 앨범 그리고 휴식기 ===
+5집 활동을 성공적으로 마친 이수영은 휴식기에 들어갔다. 소속사가 이미 일본 진출을 선언한 터라
+다른 작업은 할 수 없었다. 공백기 동안 이수영은 대한민국 가요계의 리메이크열풍의 시발점이 된 《[[Classic the remake]]》를 발매하고 [[이문세]]의 광화문 연가를 리메이크해 첫 싱글로 내세웠다.
+앨범이 발매된 직후 이수영은 미국에서 유학중이던 절친한 친구 [[박경림]]과 미국 여행을 떠났음과 동시에 방송 활동이 전혀 없었음에도 불구하고 리메이크 앨범의 시장에서의 반응은 매우 뜨거웠다.
+
+이 앨범은 2004년 앨범판매량 2위를 기록하여 그녀에게 [[2004년]] [[골든디스크]] 대상이라는 영예를 안겨주었고 특히 중,장년층에 전폭적인 지지를 받았다. 또한 이 앨범은 CD와 테이프의 판매 비율이 거의 비슷한 경우를 찾아보기 힘든 유래없는 기현상을 보였다.휴식기동안 이수영은 여행을 통해 몸과 마음을 재충전하고 귀국했다. 그녀에게는 일본시장 진출이라는 거대한 산이 기다리고 있었다.
+
+=== 일본 진출 ===
+국내에서의 성공에 자신감을 얻은 [[이가 엔터테인먼트]]는 이수영의 일본진출을 오래 전부터 계획해왔다. [[2004년]] [[5월]] 소속사는 충만한 자신감으로 [[소니 뮤직]]과 정규앨범 1장,싱글앨범 2장 발매를 조건으로 계약을 채결하고 본격적인 일본 진출을 감행했으나 결과는 참담했다.
+
+일본에서 발매한 싱글앨범인 《[[사이고노 와가마마|最後の我がまま]]》는 오리콘 차트 125위라는 저조한 성적을 기록했는데 일본앨범은 이수영 고유의 한(恨)의 정서가 결여된, 너무나도 일본음악적인 앨범이었다는 평이 지배적이었다. 색을 잃어버린 앨범이 실패하는 것은 당연한 일이었다. 일본 시장에서의 실패로 인해 이수영은 한 발 후퇴하게 되었다.
+
+소속사는 곧 국내 정규 6집 녹음에 착수했고 6집은 판매량 면에서는 호조였으나 대중들의 평 면에서는 그리 좋지 못한 반응을 받게 되었다. 일본 진출 전 과도한 자신감을 가지고 있던 소속사는 '일본 시장에서 성공할 때까지 돌아오지 않겠다.' 라는 식의 보도자료를 언론사에 마구 송고했는데 이로 인해 이수영은 본의 아니게 일부 대중들에게 '성공할 때까지 안돌아온다며 아듀 콘서트라는 이름으로 콘서트까지 한 사람이 몇개월 만에 돌아오냐'는 비난을 받았다.
+
+일본시장 진출은 소속사의 과도한 욕심과 자신감이 빚어낸 실패였다.
+
+=== 6집 ===
+6집 《[[The colors of my life]]》는 전체적으로 듣기 편안한 음악들로 이루어졌고 이는 이수영이 지향하는 음악이다. 6집의 대중들의 평은 전 앨범들에 비해 그리 좋지 못한 편이었다. 그러나 앨범 판매량에 있어서는 [[2004년]] [[9월]]에 발매되었음에도 불구하고 [[12월]]까지 무려 34만장의 판매고를 올려 그 해 앨범판매량 3위를 차지했다.(2위 또한 그녀의 앨범인 《[[Classic the remake]]》였다.)
+
+첫 번째 싱글은 〈휠릴리〉였다. 6집 활동에서는 전 앨범에 비해 더욱 더 강해진 물량 공세 홍보를 선보였고 소속사인 [[이가 엔터테인먼트]]은 각종 예능 프로그램의 크레딧타임에 뮤직비디오를 방영하였는데 그 수가 대단히 많은 것이어서 채널만 돌리면 〈휠릴리〉가 흘러나왔다. 이 물량 공세 홍보방식은 이가 엔터테인먼트가 [[2006년]] 데뷔시킨 [[아이비]]를 홍보하기 위해 사용하기도 하였다. 이런 물량 공세 속에 판매량은 호조를 보였다.
+
+6집 활동 당시에 또 다시 논란이 된 부분이 있었으니 바로 그녀의 라이브 실력이었다. 일부 대중들이 그녀의 라이브 실력에 대해 의구심을 품었는데 그들의 주장에 따르면 이수영은 5집부터 라이브 실력이 현저하게 낮아졌다는 것이다.
+이 논란은 [[KBS]] [[한국 방송]]의 프로그램인 [[윤도현의 러브레터]]에서 보인 무대로 인해 더욱 더 증폭되었고 이러한 논란으로 인해 이수영은 이듬 해 미국으로 건너가 보컬 트레이닝을 받았다.
+
+6집 활동은 무슨 이유에서인지 첫 번째 싱글인 〈휠릴리〉활동 후 중단되었으며 앨범 발매 전에 미리 두 번째 싱글로 내정되었던 〈Andante〉의 활동은 전혀 이루어지지 않았다. 이수영과 소속사의 계약만료시점이 다가옴에 따라 생긴 일이 아니냐는 추측도 있었다. 아무튼 그 해 이수영은 다시금 대한민국 최고의 가수 자리에 올랐다. 2004년 골든디스크 대상을 수상했으며 MBC 10대 가수가요제 최우수가수상을 2년 연속 수상한 것이다. [[2003년]]에 그러했듯이 이수영은 [[2005년]] [[1월 1일]]을 기해 6집 활동을 마무리지었다.
+
+=== 공백기의 이수영 ===
+[[이가 엔터테인먼트]]와의 계약이 만료되자 이수영은 미국으로 떠나 친구인 [[박경림]]과 미국 여행을 했다.
+
+이수영과 [[팬텀]](구 [[이가 엔터테인먼트]])의 계약만료 소식이 전해지자 각 언론들은 그녀의 행보에 주목했다. 남을 것인가 옮길 것인가. 그녀의 선택에 이목이 집중되었으나 이미 마음을 정한 것으로 보였다. 팬텀은 이수영의 그런 의사를 알고 매우 안좋은 모습을 보여줬다. 그것은 [[2005년]] [[2월]]에 열린 '톡톡콘서트'에서 여지없이 드러났다.
+
+'톡톡콘서트'가 있다는 사실을 이수영은 공연 3일 전에 알게 되었다. 그 전까지 이수영은 [[미국]]으로 떠나 친구 [[박경림]]과 생활하던 중이어서 준비 한번 된 적 없는 공연이 제대로 되었을 리가 만무했다.
+반주는 모두 MR을 사용했고 노래 또한 5곡만 불렀으며 토크가 주를 이뤘다. 공연 마지막에 이수영은 이 공연에 대한 진실을 이야기하면서 이 공연이 팬텀과의 마지막 일이라고 밝혔다.
+
+팬텀은 이수영이 미국에 있을 때 그녀의 의사와는 상관없이 스폐셜 앨범 《[[As time goes by]]》를 발매했다.
+첫 번째 싱글은 〈꽃들은 지고〉였다. 리메이크 앨범 때와 마찬가지고 방송 활동이 없었음에도 공중파 가요프로그램 및 각종 챠트에서 1위,2위를 다퉜다.
+
+위 두 일을 끝으로 이수영과의 일은 마무리 되는 듯 했으나 그 해 [[10월]] 이가스폐셜앨범 사건으로
+다시금 안좋은 모습을 보여줬다.
+
+[[2005년]] [[4월]]까지 휴식을 취한 이수영은 [[4월 22일]] [[리쿠드 엔터테인먼트]]와 3년간 정규 앨범 3장, 비정규 앨범 2장 발매의 조건으로 계약을 체결한다. 이수영의 새 소속사 후보로는 [[신나라 레코드]]와 리쿠드 엔터테인먼트가 물망에 떠올랐는데 이수영은 신생기획사인 리쿠드 엔터테인먼트를 택했다. 리쿠드 엔터테인먼트는 사실 신생기획사는 아니지만 매니지먼트 분야에서는 매우 초보적인 단계였다. 리쿠드 엔터테인먼트는 직원 전원이 [[개신교]] 신자로서 그 점이 그 곳을 선택하는데 영향을 미쳤다고 후에 밝혔다.
+
+계약 체결 후 미국으로 떠났는데 그곳에서 한인 축제 행사 등에 참여한 뒤 이수영은 보컬 트레이닝에 임했다. 미국 보컬 트레이너는 이수영에게 "당신은 흑인 아니라서 절대 흑인의 목소리를 낼 수 없다"며 창법이나 음색을 바꾸려 하지 말고 더욱 발전 시킬것을 조언했다.
+[[image:Sooyoungincamp.jpg|thumb|350px|팬미팅 캠프에서 노래를 부르고 있는 이수영]]
+한 달여 간의 트레이닝을 마친 이수영은 국내로 귀국하여 팬들과 만남을 계획하여 [[2005년]] [[7월 30일]], [[경기도]] [[안성시]]에서 이수영은 데뷔 후 처음으로 팬들과 팬미팅 겸 [[캠프]] 행사를 가졌다. 이 날 이수영은 팬들에게 하고 싶었던 이야기를 토해내며 마지막으로 이가 엔터테인먼트에서 추가로 발매할 세 장의 앨범에 대해 언급하며 그것은 자신의 앨범이 아니며 꼭 7집만을 사랑해 달라며 부탁했다.
+
+10월에 발매예정이어었던 7집이 이듬 해로 연기되면서 이수영은 또 다시 약 6개월간의 공백기를 가지게 되었다. 그 기간 동안 7집의 부족한 부분을 보완함과 동시에 충분한 휴식을 취하며 시간을 보냈다.
+
+=== 7집 발매 연기 ===
+전 소속사였던 [[팬텀]]은 언론에 공시를 통해 추가로 3장의 이수영 앨범을 발매하며 이는 법적으로 문제가 없다고 하였다. 하지만 전소속사 팬텀과 이수영 사이에 이미 존재했었던 계약이었는지 어떤 모종의 새로운 계약이 있었는지는 밝혀진 바가 없다.
+
+얼마 후 [[9월 30일]]을 기해 7집 앨범의 인터넷 예약 판매가 시작되었고 그와 동시에 온갖 추측이 난무했던
+팬텀의 첫 번째 앨범 (일명 이가스폐셜앨범)도 동시에 예약 판매를 시작했다. 아이러니하게도 7집은 발매일이 [[10월 21일]]있고 이가스폐셜앨범은 [[10월 20일]]이어서 7집 음반판매에 같이 묻어가려 한다는 속셈이 적나라하게 노출되었다. 이로 인해 팬텀에 대한 비난이 쏟아졌다. 이수영 측은 7집 발매를 11월로 연기하였다가 내년에 발매하는 것으로 결정내렸고 이로 인해 7집은 [[2006년]] [[1월 21일]] 발매되었다.
+
+팬텀이 발매한 이가스폐셜앨범은 기존 곡들과 일본 싱글제작당시 녹음한 미공개곡 두 곡과 기존에 공개했던 일본어곡 한 곡을 포함한 베스트앨범이었다. 시장에서의 반응은 최악이었고 이수영의 열렬한 팬들조차 등을 돌린 앨범은 역대 최저 판매량을 기록하였다. 그러나 그 판매량은 다른 신인,기존 가수들의 판매량을 우습게 뛰어 넘는 것이어서 이수영의 네임벨류가 얼마나 강한지를 실감케 했다.
+
+==== 동요 스트리밍 사건 ====
+빠빠라빠 스트리밍 사건은 2006년 1월 7집 앨범 발매를 앞두고 발생한 사건이다.
+
+어느 날 일부 스트리밍 사이트에 이수영이 2001년 촬영했던 어린이 동요 테이프인 《빠빠라빠》,《WOW Go Go English》의 음원이 추출되어 스트리밍 되었다. 우연히도 이것은 곧 두장의 앨범을 뜻하며 팬텀의 추가 앨범 발매가 아니냐는 의혹이 일었다. 이로 인해 세 장의 앨범 발매가 성립되니 그렇게 생각하는 것도 당연한 것이었다.
+팬텀에 대한 비난여론이 더욱 더 거세지자 팬텀은 보도자료를 통해 이 사건은 전적으로 동요 테이프 유통사였던
+[[문화미디콤]]의 책임이라며 맞대응하겠다는 의사를 표명했다. 또한 자신들은 이 사건과 아무런 관련이 없음을 해명했다.
+
+얼마 후 [[문화미디콤]]은 스트리밍 서비스를 중단하였다. 그리하여 빠빠라빠 스트리밍 사건은 끝을 맺었다.
+
+=== 7집 ===
+〈Grace〉는 앨범 발매 한 달만에 방송횟수 1000회를 돌파하는 기염을 토했으며 각종 음반쇼핑몰, 차트에서 수 많은 1위를 차지했다. 특히 MBC 라디오 프로그램 [[정오의 희망곡]]에서는 7주간 1위를 차지했으며 음악 스트리밍 사이트 [[벅스 뮤직]]에서는 8주간 1위라는 부동의 자리를 지켰다.
+
+〈Grace〉 활동 시 그녀는 이미지 변신을 꾀했다. 각종 예능 프로그램에 많이 출연을 한 것이다.
+이수영은 본인 스스로가 '웃긴 사람'이라고 칭할 만큼 유머감각이 뛰어난 사람이나 전 소속사인 [[팬텀]]의
+이미지관리로 인해 많은 모습을 숨기고 있었다. 이수영의 색다른 모습이 사람들의 의견이 분분했으나 이수영은 이미 [[2002년]] [[감성시대]] DJ를 하면서 그녀의 유머감각을 여지없이 선보인 적이 있다. 두 번째 싱글 활동에서는 예능 프로그램 출연을 자제하고 음악 프로그램 출연에 더 전념한다는 의사를 밝혔다.
+
+두 번째 싱글〈시린〉으로 활동중이던 이수영은 하반기 앨범 작업과 각종 국내외 공연 일정을 이유로 [[2006년]] [[4월 9일]]자로 7집의 공식적인 활동을 마감했다.
+
+=== 소속사와의 불화 ===
+[[2006년]] [[7월]] 말, 소속사인 리쿠드엔터테인먼트는 항간에 떠도는 이수영의 소속사간의 불화설은 사실무근이라는 보도자료를 언론사에 내보냈다. 그러나 이 루머에 대해서는 일반 대중이 알고 있던 사실이 아니였기에 루머 관련 기사 이전의 '사실무근기사'는 뭔가 앞뒤가 맞지 않는 상황이었다.
+
+[[7월 31일]] 이수영측은 언론사와의 인터뷰를 통해 소속사간의 불화사실을 공식발표했다. 소속사 리쿠드엔터테인먼트는 미숙한 업무처리로 인해 이수영에게 계약금을 지급하지 않았으며, 계약금 지급을 요구한 이수영의 요구에 이수영의 매니저팀을 전원해고하는 등의 조치를 취했다고 한다.
+
+또한 [[2005년]] 하반기의 전국투어 콘서트일정을 이수영의 동의없이 일방적으로 체결하는 등 여러면에서 불성실한 계약이행을 해왔다는 것이다.
+
+현재 이수영은 소속사를 상대로 소송을 한 상태로 재판을 앞두고 있는 상태다. 리쿠드 엔터테인먼트 또한 이수영을 맞고소하였다. 이수영은 7집 《[[Grace]]》의 음원권리를 넘긴다면 고소를 취하하겠다는 의사를 밝혔다.
+
+== 음악 스타일 ==
+싱글로 활동한 곡들은 대부분 발라드이다. 그러나 〈그리고 사랑해〉,〈덩그러니〉의 경우는 발라드라기 보다는 [[록]] 음악에 가깝다. 앨범에 수록된 곡들을 살펴보면 정통발라드,[[보사노바]],[[재즈]],[[포크 록]],[[펑키]],[[디스코]] 등 여러 장르가 수록 되어있으나 대부분이 서정적이고 애절한 분위기의 발라드곡이다.
+
+=== 이수영이 지향하는 음악 스타일 ===
+이수영은 [[흑인 음악]]의 열렬한 팬이다. 따라서 그녀가 지향하는 음악 스타일은 듣기 편하고 부담없는 소위 '[[이지 리스닝]]' 음악이다. 정규 7집 《Grace》 발매 후 언론과 가진 인터뷰에서 그녀는 미래의 이수영은 보컬에 더 힘을 뺄 것이라 밝혔다.
+
+== 그녀에 대한 생각 ==
+가수 그리고 연예인으로서 그녀가 지니고 있는 여러가지 요소들은 화려하거나 요란하지 않다. 그리고, 대중의 주목을 끌어낼 특별한 요소도 재주도 없다.
+그러나, 주목받는 인기가수가 되면서부터 현재의 스타시스템에서 그녀가 가지는 인지도에 대한 언급이 있었다. 어떤 사람들은 어려운 어휘와 문장을 사용하여 분석적으로 기술한다. 또 어떤 사람들은 그 인기에 대해 그럴듯한 해석을 하기 어려워 "미스테리"라고도 한다
+
+=== 장점인 동시에 단점인 평범 ===
+목소리는 일반적인데도 사람들은 각별하게 여긴다. 사실 그녀보다 두드러지는 목소리를 가진 가수는 많다.
+이미지는 부담없고 정감있는 무난한 외모에 편안하다. 연예인으로 튀지 않지만 손색없다.
+노래는 특별히 좋아하지 않아도 계속 듣고 부르게 되는 선율이 있다. 쉽지 않은 노래지만 많은 사람들이 선호하게 될 정도로 귀에 잘 들어온다.
+가사는 그 자체도 좋지만 표현력과 전달력이 뛰어나 마음에 닿는다. 여느 가수처럼 기교나 성량이 두드러지면 가사집을 보아야 무슨 말인지 알아 듣는 경우가 있다.
+음반은 흔히들 말하는 타이틀곡, 후속곡 등 일부 밀어주는 노래에 치우치지 않는다. 일반 사람들이 잘 모르는 좋은 수록곡이 많다.
+가수가 좋아서 노래를 좋아하게 되는 것보다 노래를 들었는데 좋아서 가수를 좋아하게 된다. 따라서, 흔히 언급되는 열성팬수는 많지 않다.
+
+== 수상 경력 ==
+* [[1995년]] MBC 라디오 '[[별이 빛나는 밤에]]' 장기자랑 장원
+* [[2002년]] 골든디스크 본상
+* [[2002년]] SBS 가요대전 본상
+* [[2002년]] KBS 가요대상 본상
+* [[2002년]] MBC [[10대 가수가요제]] 본상
+* [[2003년]] 골든디스크 본상
+* [[2003년]] SBS 가요대전 본상
+* [[2003년]] KBS 가요대상 본상
+* [[2003년]] MBC 10대가수가요제 최고인기가수상
+* [[2004년]] 서울 가요 대상
+* [[2004년]] [[골든디스크]] 본상 및 대상
+* [[2004년]] SBS 가요대전 본상
+* [[2004년]] KBS 가요대상 본상.
+* [[2004년]] MBC 10대 가수가요제 최고인기가수상
+
+== 발매 앨범 ==
+* '''정규 앨범'''
+** [[1999년]] 1집 《[[I Believe]]》
+** [[2001년]] 2집 《[[Never again]]》
+** [[2001년]] 3집 《[[Made in winter]]》
+** [[2002년]] 4집 《[[My stay in Sendai]]》
+** [[2003년]] 5집 《[[This time]]》
+** [[2004년]] 일본 싱글 《[[사이고노 와가마마|最後の我がまま]]》
+** [[2004년]] 6집 《[[The colors of my life]]》
+** [[2006년]] 7집 《[[Grace]]》
+
+* '''비정규 앨범'''
+** [[2001년]] 라이브앨범 《[[그녀에게 감사해요]]》
+** [[2003년]] 4.5집 《[[Sweet holiday in Lombok]]》
+** [[2004년]] 5.5집 《[[Classic the remake]]》
+** [[2005년]] 6.5집 《[[As time goes by]]》
+** [[2005년]] 2005 스폐셜《[[An Autumn Day]]》
+
+*'''참여 앨범'''
+** [[2000년]] 《이정봉 4집》
+** [[2000년]] 《Dream Song》
+** [[2001년]] 《KOREAN DANCE TOP 30》
+** [[2001년]] 《이영애의 애수》
+** [[2001년]] 《드라마 명성황후 OST》
+** [[2001년]] 《첫사랑》
+** [[2002년]] 《Baek ji Young Best & Live New Release》
+** [[2002년]] 《성룡 夢想的天材》
+** [[2002년]] 《영화 묻지마 패밀리 OST》
+** [[2002년]] 《Final Fantasy X International OST》
+** [[2002년]] 《The Winter》
+** [[2003년]] 《IVY MEGAMIX》
+** [[2003년]] 《ONE SHOT 가요리믹스 VOL.2》
+** [[2003년]] 《장나라와 친구들》
+** [[2003년]] 《드라마 장희빈 OST》
+** [[2004년]] 《LEEJEEHOON 5》
+** [[2004년]] Simply Sunday 싱글 《사랑해요》
+** [[2004년]] 《new Kpop compilation》
+** [[2004년]] 《2004* Christmas carol Ballad & Trot & Hiphop & Jazz》
+** [[2005년]] 《이 죽일놈의 사랑 OST》
+** [[2005년]] 《DJ 처리의 Chirstmas 쏭쏭쏭》
+** [[2005년]] 《The wave Christmas Caroling》
+** [[2005년]] 《The Wave Blessing & Caroling》
+
+== 바깥 고리 ==
+* [http://www.leesooyoung.com/ 공식 홈페이지]
+* [http://cafe.daum.net/suyounglove/ 다음 카페 이수영을 사랑하는 사람들]
+* [http://www.sooyoung.pe.kr/ 이수영 매니아 영상클럽]
+* [http://cafe.daum.net/fanclubgrace/ 팬클럽 그레이스 공식 카페]
+
+[[분류:1979년 태어남]]
+[[분류:대한민국의 가수]]
+[[분류:발라드]]
+[[분류:골든디스크 대상 수상 음악가]]
+[[분류:MBC 10대 가수가요제 최우수인기상 수상 음악가]]
+
+[[en:Lee Soo Young]]
+[[ja:イ・スヨン]] \ No newline at end of file
diff --git a/vendor/wikimedia/utfnormal/src/Constants.php b/vendor/wikimedia/utfnormal/src/Constants.php
new file mode 100644
index 00000000..02878a0e
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/src/Constants.php
@@ -0,0 +1,103 @@
+<?php
+namespace UtfNormal;
+
+/**
+ * Some constant definitions for the unicode normalization module.
+ *
+ * Note: these constants must all be resolvable at compile time by HipHop,
+ * since this file will not be executed during request startup for a compiled
+ * MediaWiki.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
+
+class Constants {
+ const UNICODE_HANGUL_FIRST = 0xac00;
+ const UNICODE_HANGUL_LAST = 0xd7a3;
+
+ const UNICODE_HANGUL_LBASE = 0x1100;
+ const UNICODE_HANGUL_VBASE = 0x1161;
+ const UNICODE_HANGUL_TBASE = 0x11a7;
+
+ const UNICODE_HANGUL_LCOUNT = 19;
+ const UNICODE_HANGUL_VCOUNT = 21;
+ const UNICODE_HANGUL_TCOUNT = 28;
+ # UNICODE_HANGUL_VCOUNT * UNICODE_HANGUL_TCOUNT
+ const UNICODE_HANGUL_NCOUNT = 588;
+
+ # UNICODE_HANGUL_LBASE + UNICODE_HANGUL_LCOUNT - 1
+ const UNICODE_HANGUL_LEND = 4370;
+ # UNICODE_HANGUL_VBASE + UNICODE_HANGUL_VCOUNT - 1
+ const UNICODE_HANGUL_VEND = 4469;
+ # UNICODE_HANGUL_TBASE + UNICODE_HANGUL_TCOUNT - 1
+ const UNICODE_HANGUL_TEND = 4546;
+
+ const UNICODE_SURROGATE_FIRST = 0xd800;
+ const UNICODE_SURROGATE_LAST = 0xdfff;
+ const UNICODE_MAX = 0x10ffff;
+ const UNICODE_REPLACEMENT = 0xfffd;
+
+ # codepointToUtf8( UNICODE_HANGUL_FIRST )
+ const UTF8_HANGUL_FIRST = "\xea\xb0\x80";
+ # codepointToUtf8( UNICODE_HANGUL_LAST )
+ const UTF8_HANGUL_LAST = "\xed\x9e\xa3";
+
+ # codepointToUtf8( UNICODE_HANGUL_LBASE )
+ const UTF8_HANGUL_LBASE = "\xe1\x84\x80";
+ # codepointToUtf8( UNICODE_HANGUL_VBASE )
+ const UTF8_HANGUL_VBASE = "\xe1\x85\xa1";
+ # codepointToUtf8( UNICODE_HANGUL_TBASE )
+ const UTF8_HANGUL_TBASE = "\xe1\x86\xa7";
+
+ # codepointToUtf8( UNICODE_HANGUL_LEND )
+ const UTF8_HANGUL_LEND = "\xe1\x84\x92";
+ # codepointToUtf8( UNICODE_HANGUL_VEND )
+ const UTF8_HANGUL_VEND = "\xe1\x85\xb5";
+ # codepointToUtf8( UNICODE_HANGUL_TEND )
+ const UTF8_HANGUL_TEND = "\xe1\x87\x82";
+
+ # codepointToUtf8( UNICODE_SURROGATE_FIRST )
+ const UTF8_SURROGATE_FIRST = "\xed\xa0\x80";
+ # codepointToUtf8( UNICODE_SURROGATE_LAST )
+ const UTF8_SURROGATE_LAST = "\xed\xbf\xbf";
+ # codepointToUtf8( UNICODE_MAX )
+ const UTF8_MAX = "\xf4\x8f\xbf\xbf";
+ # codepointToUtf8( UNICODE_REPLACEMENT )
+ const UTF8_REPLACEMENT = "\xef\xbf\xbd";
+ #const UTF8_REPLACEMENT = '!';
+
+ const UTF8_OVERLONG_A = "\xc1\xbf";
+ const UTF8_OVERLONG_B = "\xe0\x9f\xbf";
+ const UTF8_OVERLONG_C = "\xf0\x8f\xbf\xbf";
+
+ # These two ranges are illegal
+ # codepointToUtf8( 0xfdd0 )
+ const UTF8_FDD0 = "\xef\xb7\x90";
+ # codepointToUtf8( 0xfdef )
+ const UTF8_FDEF = "\xef\xb7\xaf";
+ # codepointToUtf8( 0xfffe )
+ const UTF8_FFFE = "\xef\xbf\xbe";
+ # codepointToUtf8( 0xffff )
+ const UTF8_FFFF = "\xef\xbf\xbf";
+
+ const UTF8_HEAD = false;
+ const UTF8_TAIL = true;
+
+}
+
diff --git a/vendor/wikimedia/utfnormal/src/UtfNormalData.inc b/vendor/wikimedia/utfnormal/src/UtfNormalData.inc
new file mode 100644
index 00000000..fae0964a
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/src/UtfNormalData.inc
@@ -0,0 +1,14 @@
+<?php
+/**
+ * This file was automatically generated -- do not edit!
+ * Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
+ */
+// @codingStandardsIgnoreFile
+
+UtfNormal\Validator::$utfCombiningClass = unserialize( 'a:606:{s:2:"̀";i:230;s:2:"́";i:230;s:2:"̂";i:230;s:2:"̃";i:230;s:2:"̄";i:230;s:2:"̅";i:230;s:2:"̆";i:230;s:2:"̇";i:230;s:2:"̈";i:230;s:2:"̉";i:230;s:2:"̊";i:230;s:2:"̋";i:230;s:2:"̌";i:230;s:2:"̍";i:230;s:2:"̎";i:230;s:2:"̏";i:230;s:2:"̐";i:230;s:2:"̑";i:230;s:2:"̒";i:230;s:2:"̓";i:230;s:2:"̔";i:230;s:2:"̕";i:232;s:2:"̖";i:220;s:2:"̗";i:220;s:2:"̘";i:220;s:2:"̙";i:220;s:2:"̚";i:232;s:2:"̛";i:216;s:2:"̜";i:220;s:2:"̝";i:220;s:2:"̞";i:220;s:2:"̟";i:220;s:2:"̠";i:220;s:2:"̡";i:202;s:2:"̢";i:202;s:2:"̣";i:220;s:2:"̤";i:220;s:2:"̥";i:220;s:2:"̦";i:220;s:2:"̧";i:202;s:2:"̨";i:202;s:2:"̩";i:220;s:2:"̪";i:220;s:2:"̫";i:220;s:2:"̬";i:220;s:2:"̭";i:220;s:2:"̮";i:220;s:2:"̯";i:220;s:2:"̰";i:220;s:2:"̱";i:220;s:2:"̲";i:220;s:2:"̳";i:220;s:2:"̴";i:1;s:2:"̵";i:1;s:2:"̶";i:1;s:2:"̷";i:1;s:2:"̸";i:1;s:2:"̹";i:220;s:2:"̺";i:220;s:2:"̻";i:220;s:2:"̼";i:220;s:2:"̽";i:230;s:2:"̾";i:230;s:2:"̿";i:230;s:2:"̀";i:230;s:2:"́";i:230;s:2:"͂";i:230;s:2:"̓";i:230;s:2:"̈́";i:230;s:2:"ͅ";i:240;s:2:"͆";i:230;s:2:"͇";i:220;s:2:"͈";i:220;s:2:"͉";i:220;s:2:"͊";i:230;s:2:"͋";i:230;s:2:"͌";i:230;s:2:"͍";i:220;s:2:"͎";i:220;s:2:"͐";i:230;s:2:"͑";i:230;s:2:"͒";i:230;s:2:"͓";i:220;s:2:"͔";i:220;s:2:"͕";i:220;s:2:"͖";i:220;s:2:"͗";i:230;s:2:"͘";i:232;s:2:"͙";i:220;s:2:"͚";i:220;s:2:"͛";i:230;s:2:"͜";i:233;s:2:"͝";i:234;s:2:"͞";i:234;s:2:"͟";i:233;s:2:"͠";i:234;s:2:"͡";i:234;s:2:"͢";i:233;s:2:"ͣ";i:230;s:2:"ͤ";i:230;s:2:"ͥ";i:230;s:2:"ͦ";i:230;s:2:"ͧ";i:230;s:2:"ͨ";i:230;s:2:"ͩ";i:230;s:2:"ͪ";i:230;s:2:"ͫ";i:230;s:2:"ͬ";i:230;s:2:"ͭ";i:230;s:2:"ͮ";i:230;s:2:"ͯ";i:230;s:2:"҃";i:230;s:2:"҄";i:230;s:2:"҅";i:230;s:2:"҆";i:230;s:2:"҇";i:230;s:2:"֑";i:220;s:2:"֒";i:230;s:2:"֓";i:230;s:2:"֔";i:230;s:2:"֕";i:230;s:2:"֖";i:220;s:2:"֗";i:230;s:2:"֘";i:230;s:2:"֙";i:230;s:2:"֚";i:222;s:2:"֛";i:220;s:2:"֜";i:230;s:2:"֝";i:230;s:2:"֞";i:230;s:2:"֟";i:230;s:2:"֠";i:230;s:2:"֡";i:230;s:2:"֢";i:220;s:2:"֣";i:220;s:2:"֤";i:220;s:2:"֥";i:220;s:2:"֦";i:220;s:2:"֧";i:220;s:2:"֨";i:230;s:2:"֩";i:230;s:2:"֪";i:220;s:2:"֫";i:230;s:2:"֬";i:230;s:2:"֭";i:222;s:2:"֮";i:228;s:2:"֯";i:230;s:2:"ְ";i:10;s:2:"ֱ";i:11;s:2:"ֲ";i:12;s:2:"ֳ";i:13;s:2:"ִ";i:14;s:2:"ֵ";i:15;s:2:"ֶ";i:16;s:2:"ַ";i:17;s:2:"ָ";i:18;s:2:"ֹ";i:19;s:2:"ֺ";i:19;s:2:"ֻ";i:20;s:2:"ּ";i:21;s:2:"ֽ";i:22;s:2:"ֿ";i:23;s:2:"ׁ";i:24;s:2:"ׂ";i:25;s:2:"ׄ";i:230;s:2:"ׅ";i:220;s:2:"ׇ";i:18;s:2:"ؐ";i:230;s:2:"ؑ";i:230;s:2:"ؒ";i:230;s:2:"ؓ";i:230;s:2:"ؔ";i:230;s:2:"ؕ";i:230;s:2:"ؖ";i:230;s:2:"ؗ";i:230;s:2:"ؘ";i:30;s:2:"ؙ";i:31;s:2:"ؚ";i:32;s:2:"ً";i:27;s:2:"ٌ";i:28;s:2:"ٍ";i:29;s:2:"َ";i:30;s:2:"ُ";i:31;s:2:"ِ";i:32;s:2:"ّ";i:33;s:2:"ْ";i:34;s:2:"ٓ";i:230;s:2:"ٔ";i:230;s:2:"ٕ";i:220;s:2:"ٖ";i:220;s:2:"ٗ";i:230;s:2:"٘";i:230;s:2:"ٙ";i:230;s:2:"ٚ";i:230;s:2:"ٛ";i:230;s:2:"ٜ";i:220;s:2:"ٝ";i:230;s:2:"ٞ";i:230;s:2:"ٟ";i:220;s:2:"ٰ";i:35;s:2:"ۖ";i:230;s:2:"ۗ";i:230;s:2:"ۘ";i:230;s:2:"ۙ";i:230;s:2:"ۚ";i:230;s:2:"ۛ";i:230;s:2:"ۜ";i:230;s:2:"۟";i:230;s:2:"۠";i:230;s:2:"ۡ";i:230;s:2:"ۢ";i:230;s:2:"ۣ";i:220;s:2:"ۤ";i:230;s:2:"ۧ";i:230;s:2:"ۨ";i:230;s:2:"۪";i:220;s:2:"۫";i:230;s:2:"۬";i:230;s:2:"ۭ";i:220;s:2:"ܑ";i:36;s:2:"ܰ";i:230;s:2:"ܱ";i:220;s:2:"ܲ";i:230;s:2:"ܳ";i:230;s:2:"ܴ";i:220;s:2:"ܵ";i:230;s:2:"ܶ";i:230;s:2:"ܷ";i:220;s:2:"ܸ";i:220;s:2:"ܹ";i:220;s:2:"ܺ";i:230;s:2:"ܻ";i:220;s:2:"ܼ";i:220;s:2:"ܽ";i:230;s:2:"ܾ";i:220;s:2:"ܿ";i:230;s:2:"݀";i:230;s:2:"݁";i:230;s:2:"݂";i:220;s:2:"݃";i:230;s:2:"݄";i:220;s:2:"݅";i:230;s:2:"݆";i:220;s:2:"݇";i:230;s:2:"݈";i:220;s:2:"݉";i:230;s:2:"݊";i:230;s:2:"߫";i:230;s:2:"߬";i:230;s:2:"߭";i:230;s:2:"߮";i:230;s:2:"߯";i:230;s:2:"߰";i:230;s:2:"߱";i:230;s:2:"߲";i:220;s:2:"߳";i:230;s:3:"ࠖ";i:230;s:3:"ࠗ";i:230;s:3:"࠘";i:230;s:3:"࠙";i:230;s:3:"ࠛ";i:230;s:3:"ࠜ";i:230;s:3:"ࠝ";i:230;s:3:"ࠞ";i:230;s:3:"ࠟ";i:230;s:3:"ࠠ";i:230;s:3:"ࠡ";i:230;s:3:"ࠢ";i:230;s:3:"ࠣ";i:230;s:3:"ࠥ";i:230;s:3:"ࠦ";i:230;s:3:"ࠧ";i:230;s:3:"ࠩ";i:230;s:3:"ࠪ";i:230;s:3:"ࠫ";i:230;s:3:"ࠬ";i:230;s:3:"࠭";i:230;s:3:"࡙";i:220;s:3:"࡚";i:220;s:3:"࡛";i:220;s:3:"़";i:7;s:3:"्";i:9;s:3:"॑";i:230;s:3:"॒";i:220;s:3:"॓";i:230;s:3:"॔";i:230;s:3:"়";i:7;s:3:"্";i:9;s:3:"਼";i:7;s:3:"੍";i:9;s:3:"઼";i:7;s:3:"્";i:9;s:3:"଼";i:7;s:3:"୍";i:9;s:3:"்";i:9;s:3:"్";i:9;s:3:"ౕ";i:84;s:3:"ౖ";i:91;s:3:"಼";i:7;s:3:"್";i:9;s:3:"്";i:9;s:3:"්";i:9;s:3:"ุ";i:103;s:3:"ู";i:103;s:3:"ฺ";i:9;s:3:"่";i:107;s:3:"้";i:107;s:3:"๊";i:107;s:3:"๋";i:107;s:3:"ຸ";i:118;s:3:"ູ";i:118;s:3:"່";i:122;s:3:"້";i:122;s:3:"໊";i:122;s:3:"໋";i:122;s:3:"༘";i:220;s:3:"༙";i:220;s:3:"༵";i:220;s:3:"༷";i:220;s:3:"༹";i:216;s:3:"ཱ";i:129;s:3:"ི";i:130;s:3:"ུ";i:132;s:3:"ེ";i:130;s:3:"ཻ";i:130;s:3:"ོ";i:130;s:3:"ཽ";i:130;s:3:"ྀ";i:130;s:3:"ྂ";i:230;s:3:"ྃ";i:230;s:3:"྄";i:9;s:3:"྆";i:230;s:3:"྇";i:230;s:3:"࿆";i:220;s:3:"့";i:7;s:3:"္";i:9;s:3:"်";i:9;s:3:"ႍ";i:220;s:3:"፝";i:230;s:3:"፞";i:230;s:3:"፟";i:230;s:3:"᜔";i:9;s:3:"᜴";i:9;s:3:"្";i:9;s:3:"៝";i:230;s:3:"ᢩ";i:228;s:3:"᤹";i:222;s:3:"᤺";i:230;s:3:"᤻";i:220;s:3:"ᨗ";i:230;s:3:"ᨘ";i:220;s:3:"᩠";i:9;s:3:"᩵";i:230;s:3:"᩶";i:230;s:3:"᩷";i:230;s:3:"᩸";i:230;s:3:"᩹";i:230;s:3:"᩺";i:230;s:3:"᩻";i:230;s:3:"᩼";i:230;s:3:"᩿";i:220;s:3:"᬴";i:7;s:3:"᭄";i:9;s:3:"᭫";i:230;s:3:"᭬";i:220;s:3:"᭭";i:230;s:3:"᭮";i:230;s:3:"᭯";i:230;s:3:"᭰";i:230;s:3:"᭱";i:230;s:3:"᭲";i:230;s:3:"᭳";i:230;s:3:"᮪";i:9;s:3:"᯦";i:7;s:3:"᯲";i:9;s:3:"᯳";i:9;s:3:"᰷";i:7;s:3:"᳐";i:230;s:3:"᳑";i:230;s:3:"᳒";i:230;s:3:"᳔";i:1;s:3:"᳕";i:220;s:3:"᳖";i:220;s:3:"᳗";i:220;s:3:"᳘";i:220;s:3:"᳙";i:220;s:3:"᳚";i:230;s:3:"᳛";i:230;s:3:"᳜";i:220;s:3:"᳝";i:220;s:3:"᳞";i:220;s:3:"᳟";i:220;s:3:"᳠";i:230;s:3:"᳢";i:1;s:3:"᳣";i:1;s:3:"᳤";i:1;s:3:"᳥";i:1;s:3:"᳦";i:1;s:3:"᳧";i:1;s:3:"᳨";i:1;s:3:"᳭";i:220;s:3:"᷀";i:230;s:3:"᷁";i:230;s:3:"᷂";i:220;s:3:"᷃";i:230;s:3:"᷄";i:230;s:3:"᷅";i:230;s:3:"᷆";i:230;s:3:"᷇";i:230;s:3:"᷈";i:230;s:3:"᷉";i:230;s:3:"᷊";i:220;s:3:"᷋";i:230;s:3:"᷌";i:230;s:3:"᷍";i:234;s:3:"᷎";i:214;s:3:"᷏";i:220;s:3:"᷐";i:202;s:3:"᷑";i:230;s:3:"᷒";i:230;s:3:"ᷓ";i:230;s:3:"ᷔ";i:230;s:3:"ᷕ";i:230;s:3:"ᷖ";i:230;s:3:"ᷗ";i:230;s:3:"ᷘ";i:230;s:3:"ᷙ";i:230;s:3:"ᷚ";i:230;s:3:"ᷛ";i:230;s:3:"ᷜ";i:230;s:3:"ᷝ";i:230;s:3:"ᷞ";i:230;s:3:"ᷟ";i:230;s:3:"ᷠ";i:230;s:3:"ᷡ";i:230;s:3:"ᷢ";i:230;s:3:"ᷣ";i:230;s:3:"ᷤ";i:230;s:3:"ᷥ";i:230;s:3:"ᷦ";i:230;s:3:"᷼";i:233;s:3:"᷽";i:220;s:3:"᷾";i:230;s:3:"᷿";i:220;s:3:"⃐";i:230;s:3:"⃑";i:230;s:3:"⃒";i:1;s:3:"⃓";i:1;s:3:"⃔";i:230;s:3:"⃕";i:230;s:3:"⃖";i:230;s:3:"⃗";i:230;s:3:"⃘";i:1;s:3:"⃙";i:1;s:3:"⃚";i:1;s:3:"⃛";i:230;s:3:"⃜";i:230;s:3:"⃡";i:230;s:3:"⃥";i:1;s:3:"⃦";i:1;s:3:"⃧";i:230;s:3:"⃨";i:220;s:3:"⃩";i:230;s:3:"⃪";i:1;s:3:"⃫";i:1;s:3:"⃬";i:220;s:3:"⃭";i:220;s:3:"⃮";i:220;s:3:"⃯";i:220;s:3:"⃰";i:230;s:3:"⳯";i:230;s:3:"⳰";i:230;s:3:"⳱";i:230;s:3:"⵿";i:9;s:3:"ⷠ";i:230;s:3:"ⷡ";i:230;s:3:"ⷢ";i:230;s:3:"ⷣ";i:230;s:3:"ⷤ";i:230;s:3:"ⷥ";i:230;s:3:"ⷦ";i:230;s:3:"ⷧ";i:230;s:3:"ⷨ";i:230;s:3:"ⷩ";i:230;s:3:"ⷪ";i:230;s:3:"ⷫ";i:230;s:3:"ⷬ";i:230;s:3:"ⷭ";i:230;s:3:"ⷮ";i:230;s:3:"ⷯ";i:230;s:3:"ⷰ";i:230;s:3:"ⷱ";i:230;s:3:"ⷲ";i:230;s:3:"ⷳ";i:230;s:3:"ⷴ";i:230;s:3:"ⷵ";i:230;s:3:"ⷶ";i:230;s:3:"ⷷ";i:230;s:3:"ⷸ";i:230;s:3:"ⷹ";i:230;s:3:"ⷺ";i:230;s:3:"ⷻ";i:230;s:3:"ⷼ";i:230;s:3:"ⷽ";i:230;s:3:"ⷾ";i:230;s:3:"ⷿ";i:230;s:3:"〪";i:218;s:3:"〫";i:228;s:3:"〬";i:232;s:3:"〭";i:222;s:3:"〮";i:224;s:3:"〯";i:224;s:3:"゙";i:8;s:3:"゚";i:8;s:3:"꙯";i:230;s:3:"꙼";i:230;s:3:"꙽";i:230;s:3:"꛰";i:230;s:3:"꛱";i:230;s:3:"꠆";i:9;s:3:"꣄";i:9;s:3:"꣠";i:230;s:3:"꣡";i:230;s:3:"꣢";i:230;s:3:"꣣";i:230;s:3:"꣤";i:230;s:3:"꣥";i:230;s:3:"꣦";i:230;s:3:"꣧";i:230;s:3:"꣨";i:230;s:3:"꣩";i:230;s:3:"꣪";i:230;s:3:"꣫";i:230;s:3:"꣬";i:230;s:3:"꣭";i:230;s:3:"꣮";i:230;s:3:"꣯";i:230;s:3:"꣰";i:230;s:3:"꣱";i:230;s:3:"꤫";i:220;s:3:"꤬";i:220;s:3:"꤭";i:220;s:3:"꥓";i:9;s:3:"꦳";i:7;s:3:"꧀";i:9;s:3:"ꪰ";i:230;s:3:"ꪲ";i:230;s:3:"ꪳ";i:230;s:3:"ꪴ";i:220;s:3:"ꪷ";i:230;s:3:"ꪸ";i:230;s:3:"ꪾ";i:230;s:3:"꪿";i:230;s:3:"꫁";i:230;s:3:"꯭";i:9;s:3:"ﬞ";i:26;s:3:"︠";i:230;s:3:"︡";i:230;s:3:"︢";i:230;s:3:"︣";i:230;s:3:"︤";i:230;s:3:"︥";i:230;s:3:"︦";i:230;s:4:"𐇽";i:220;s:4:"𐨍";i:220;s:4:"𐨏";i:230;s:4:"𐨸";i:230;s:4:"𐨹";i:1;s:4:"𐨺";i:220;s:4:"𐨿";i:9;s:4:"𑁆";i:9;s:4:"𑂹";i:9;s:4:"𑂺";i:7;s:4:"𝅥";i:216;s:4:"𝅦";i:216;s:4:"𝅧";i:1;s:4:"𝅨";i:1;s:4:"𝅩";i:1;s:4:"𝅭";i:226;s:4:"𝅮";i:216;s:4:"𝅯";i:216;s:4:"𝅰";i:216;s:4:"𝅱";i:216;s:4:"𝅲";i:216;s:4:"𝅻";i:220;s:4:"𝅼";i:220;s:4:"𝅽";i:220;s:4:"𝅾";i:220;s:4:"𝅿";i:220;s:4:"𝆀";i:220;s:4:"𝆁";i:220;s:4:"𝆂";i:220;s:4:"𝆅";i:230;s:4:"𝆆";i:230;s:4:"𝆇";i:230;s:4:"𝆈";i:230;s:4:"𝆉";i:230;s:4:"𝆊";i:220;s:4:"𝆋";i:220;s:4:"𝆪";i:230;s:4:"𝆫";i:230;s:4:"𝆬";i:230;s:4:"𝆭";i:230;s:4:"𝉂";i:230;s:4:"𝉃";i:230;s:4:"𝉄";i:230;}' );
+UtfNormal\Validator::$utfCanonicalComp = unserialize( 'a:1868:{s:3:"À";s:2:"À";s:3:"Á";s:2:"Á";s:3:"Â";s:2:"Â";s:3:"Ã";s:2:"Ã";s:3:"Ä";s:2:"Ä";s:3:"Å";s:2:"Å";s:3:"Ç";s:2:"Ç";s:3:"È";s:2:"È";s:3:"É";s:2:"É";s:3:"Ê";s:2:"Ê";s:3:"Ë";s:2:"Ë";s:3:"Ì";s:2:"Ì";s:3:"Í";s:2:"Í";s:3:"Î";s:2:"Î";s:3:"Ï";s:2:"Ï";s:3:"Ñ";s:2:"Ñ";s:3:"Ò";s:2:"Ò";s:3:"Ó";s:2:"Ó";s:3:"Ô";s:2:"Ô";s:3:"Õ";s:2:"Õ";s:3:"Ö";s:2:"Ö";s:3:"Ù";s:2:"Ù";s:3:"Ú";s:2:"Ú";s:3:"Û";s:2:"Û";s:3:"Ü";s:2:"Ü";s:3:"Ý";s:2:"Ý";s:3:"à";s:2:"à";s:3:"á";s:2:"á";s:3:"â";s:2:"â";s:3:"ã";s:2:"ã";s:3:"ä";s:2:"ä";s:3:"å";s:2:"å";s:3:"ç";s:2:"ç";s:3:"è";s:2:"è";s:3:"é";s:2:"é";s:3:"ê";s:2:"ê";s:3:"ë";s:2:"ë";s:3:"ì";s:2:"ì";s:3:"í";s:2:"í";s:3:"î";s:2:"î";s:3:"ï";s:2:"ï";s:3:"ñ";s:2:"ñ";s:3:"ò";s:2:"ò";s:3:"ó";s:2:"ó";s:3:"ô";s:2:"ô";s:3:"õ";s:2:"õ";s:3:"ö";s:2:"ö";s:3:"ù";s:2:"ù";s:3:"ú";s:2:"ú";s:3:"û";s:2:"û";s:3:"ü";s:2:"ü";s:3:"ý";s:2:"ý";s:3:"ÿ";s:2:"ÿ";s:3:"Ā";s:2:"Ā";s:3:"ā";s:2:"ā";s:3:"Ă";s:2:"Ă";s:3:"ă";s:2:"ă";s:3:"Ą";s:2:"Ą";s:3:"ą";s:2:"ą";s:3:"Ć";s:2:"Ć";s:3:"ć";s:2:"ć";s:3:"Ĉ";s:2:"Ĉ";s:3:"ĉ";s:2:"ĉ";s:3:"Ċ";s:2:"Ċ";s:3:"ċ";s:2:"ċ";s:3:"Č";s:2:"Č";s:3:"č";s:2:"č";s:3:"Ď";s:2:"Ď";s:3:"ď";s:2:"ď";s:3:"Ē";s:2:"Ē";s:3:"ē";s:2:"ē";s:3:"Ĕ";s:2:"Ĕ";s:3:"ĕ";s:2:"ĕ";s:3:"Ė";s:2:"Ė";s:3:"ė";s:2:"ė";s:3:"Ę";s:2:"Ę";s:3:"ę";s:2:"ę";s:3:"Ě";s:2:"Ě";s:3:"ě";s:2:"ě";s:3:"Ĝ";s:2:"Ĝ";s:3:"ĝ";s:2:"ĝ";s:3:"Ğ";s:2:"Ğ";s:3:"ğ";s:2:"ğ";s:3:"Ġ";s:2:"Ġ";s:3:"ġ";s:2:"ġ";s:3:"Ģ";s:2:"Ģ";s:3:"ģ";s:2:"ģ";s:3:"Ĥ";s:2:"Ĥ";s:3:"ĥ";s:2:"ĥ";s:3:"Ĩ";s:2:"Ĩ";s:3:"ĩ";s:2:"ĩ";s:3:"Ī";s:2:"Ī";s:3:"ī";s:2:"ī";s:3:"Ĭ";s:2:"Ĭ";s:3:"ĭ";s:2:"ĭ";s:3:"Į";s:2:"Į";s:3:"į";s:2:"į";s:3:"İ";s:2:"İ";s:3:"Ĵ";s:2:"Ĵ";s:3:"ĵ";s:2:"ĵ";s:3:"Ķ";s:2:"Ķ";s:3:"ķ";s:2:"ķ";s:3:"Ĺ";s:2:"Ĺ";s:3:"ĺ";s:2:"ĺ";s:3:"Ļ";s:2:"Ļ";s:3:"ļ";s:2:"ļ";s:3:"Ľ";s:2:"Ľ";s:3:"ľ";s:2:"ľ";s:3:"Ń";s:2:"Ń";s:3:"ń";s:2:"ń";s:3:"Ņ";s:2:"Ņ";s:3:"ņ";s:2:"ņ";s:3:"Ň";s:2:"Ň";s:3:"ň";s:2:"ň";s:3:"Ō";s:2:"Ō";s:3:"ō";s:2:"ō";s:3:"Ŏ";s:2:"Ŏ";s:3:"ŏ";s:2:"ŏ";s:3:"Ő";s:2:"Ő";s:3:"ő";s:2:"ő";s:3:"Ŕ";s:2:"Ŕ";s:3:"ŕ";s:2:"ŕ";s:3:"Ŗ";s:2:"Ŗ";s:3:"ŗ";s:2:"ŗ";s:3:"Ř";s:2:"Ř";s:3:"ř";s:2:"ř";s:3:"Ś";s:2:"Ś";s:3:"ś";s:2:"ś";s:3:"Ŝ";s:2:"Ŝ";s:3:"ŝ";s:2:"ŝ";s:3:"Ş";s:2:"Ş";s:3:"ş";s:2:"ş";s:3:"Š";s:2:"Š";s:3:"š";s:2:"š";s:3:"Ţ";s:2:"Ţ";s:3:"ţ";s:2:"ţ";s:3:"Ť";s:2:"Ť";s:3:"ť";s:2:"ť";s:3:"Ũ";s:2:"Ũ";s:3:"ũ";s:2:"ũ";s:3:"Ū";s:2:"Ū";s:3:"ū";s:2:"ū";s:3:"Ŭ";s:2:"Ŭ";s:3:"ŭ";s:2:"ŭ";s:3:"Ů";s:2:"Ů";s:3:"ů";s:2:"ů";s:3:"Ű";s:2:"Ű";s:3:"ű";s:2:"ű";s:3:"Ų";s:2:"Ų";s:3:"ų";s:2:"ų";s:3:"Ŵ";s:2:"Ŵ";s:3:"ŵ";s:2:"ŵ";s:3:"Ŷ";s:2:"Ŷ";s:3:"ŷ";s:2:"ŷ";s:3:"Ÿ";s:2:"Ÿ";s:3:"Ź";s:2:"Ź";s:3:"ź";s:2:"ź";s:3:"Ż";s:2:"Ż";s:3:"ż";s:2:"ż";s:3:"Ž";s:2:"Ž";s:3:"ž";s:2:"ž";s:3:"Ơ";s:2:"Ơ";s:3:"ơ";s:2:"ơ";s:3:"Ư";s:2:"Ư";s:3:"ư";s:2:"ư";s:3:"Ǎ";s:2:"Ǎ";s:3:"ǎ";s:2:"ǎ";s:3:"Ǐ";s:2:"Ǐ";s:3:"ǐ";s:2:"ǐ";s:3:"Ǒ";s:2:"Ǒ";s:3:"ǒ";s:2:"ǒ";s:3:"Ǔ";s:2:"Ǔ";s:3:"ǔ";s:2:"ǔ";s:4:"Ǖ";s:2:"Ǖ";s:4:"ǖ";s:2:"ǖ";s:4:"Ǘ";s:2:"Ǘ";s:4:"ǘ";s:2:"ǘ";s:4:"Ǚ";s:2:"Ǚ";s:4:"ǚ";s:2:"ǚ";s:4:"Ǜ";s:2:"Ǜ";s:4:"ǜ";s:2:"ǜ";s:4:"Ǟ";s:2:"Ǟ";s:4:"ǟ";s:2:"ǟ";s:4:"Ǡ";s:2:"Ǡ";s:4:"ǡ";s:2:"ǡ";s:4:"Ǣ";s:2:"Ǣ";s:4:"ǣ";s:2:"ǣ";s:3:"Ǧ";s:2:"Ǧ";s:3:"ǧ";s:2:"ǧ";s:3:"Ǩ";s:2:"Ǩ";s:3:"ǩ";s:2:"ǩ";s:3:"Ǫ";s:2:"Ǫ";s:3:"ǫ";s:2:"ǫ";s:4:"Ǭ";s:2:"Ǭ";s:4:"ǭ";s:2:"ǭ";s:4:"Ǯ";s:2:"Ǯ";s:4:"ǯ";s:2:"ǯ";s:3:"ǰ";s:2:"ǰ";s:3:"Ǵ";s:2:"Ǵ";s:3:"ǵ";s:2:"ǵ";s:3:"Ǹ";s:2:"Ǹ";s:3:"ǹ";s:2:"ǹ";s:4:"Ǻ";s:2:"Ǻ";s:4:"ǻ";s:2:"ǻ";s:4:"Ǽ";s:2:"Ǽ";s:4:"ǽ";s:2:"ǽ";s:4:"Ǿ";s:2:"Ǿ";s:4:"ǿ";s:2:"ǿ";s:3:"Ȁ";s:2:"Ȁ";s:3:"ȁ";s:2:"ȁ";s:3:"Ȃ";s:2:"Ȃ";s:3:"ȃ";s:2:"ȃ";s:3:"Ȅ";s:2:"Ȅ";s:3:"ȅ";s:2:"ȅ";s:3:"Ȇ";s:2:"Ȇ";s:3:"ȇ";s:2:"ȇ";s:3:"Ȉ";s:2:"Ȉ";s:3:"ȉ";s:2:"ȉ";s:3:"Ȋ";s:2:"Ȋ";s:3:"ȋ";s:2:"ȋ";s:3:"Ȍ";s:2:"Ȍ";s:3:"ȍ";s:2:"ȍ";s:3:"Ȏ";s:2:"Ȏ";s:3:"ȏ";s:2:"ȏ";s:3:"Ȑ";s:2:"Ȑ";s:3:"ȑ";s:2:"ȑ";s:3:"Ȓ";s:2:"Ȓ";s:3:"ȓ";s:2:"ȓ";s:3:"Ȕ";s:2:"Ȕ";s:3:"ȕ";s:2:"ȕ";s:3:"Ȗ";s:2:"Ȗ";s:3:"ȗ";s:2:"ȗ";s:3:"Ș";s:2:"Ș";s:3:"ș";s:2:"ș";s:3:"Ț";s:2:"Ț";s:3:"ț";s:2:"ț";s:3:"Ȟ";s:2:"Ȟ";s:3:"ȟ";s:2:"ȟ";s:3:"Ȧ";s:2:"Ȧ";s:3:"ȧ";s:2:"ȧ";s:3:"Ȩ";s:2:"Ȩ";s:3:"ȩ";s:2:"ȩ";s:4:"Ȫ";s:2:"Ȫ";s:4:"ȫ";s:2:"ȫ";s:4:"Ȭ";s:2:"Ȭ";s:4:"ȭ";s:2:"ȭ";s:3:"Ȯ";s:2:"Ȯ";s:3:"ȯ";s:2:"ȯ";s:4:"Ȱ";s:2:"Ȱ";s:4:"ȱ";s:2:"ȱ";s:3:"Ȳ";s:2:"Ȳ";s:3:"ȳ";s:2:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:4:"̈́";s:2:"̈́";s:2:"ʹ";s:2:"ʹ";s:1:";";s:2:";";s:4:"΅";s:2:"΅";s:4:"Ά";s:2:"Ά";s:2:"·";s:2:"·";s:4:"Έ";s:2:"Έ";s:4:"Ή";s:2:"Ή";s:4:"Ί";s:2:"Ί";s:4:"Ό";s:2:"Ό";s:4:"Ύ";s:2:"Ύ";s:4:"Ώ";s:2:"Ώ";s:4:"ΐ";s:2:"ΐ";s:4:"Ϊ";s:2:"Ϊ";s:4:"Ϋ";s:2:"Ϋ";s:4:"ά";s:2:"ά";s:4:"έ";s:2:"έ";s:4:"ή";s:2:"ή";s:4:"ί";s:2:"ί";s:4:"ΰ";s:2:"ΰ";s:4:"ϊ";s:2:"ϊ";s:4:"ϋ";s:2:"ϋ";s:4:"ό";s:2:"ό";s:4:"ύ";s:2:"ύ";s:4:"ώ";s:2:"ώ";s:4:"ϓ";s:2:"ϓ";s:4:"ϔ";s:2:"ϔ";s:4:"Ѐ";s:2:"Ѐ";s:4:"Ё";s:2:"Ё";s:4:"Ѓ";s:2:"Ѓ";s:4:"Ї";s:2:"Ї";s:4:"Ќ";s:2:"Ќ";s:4:"Ѝ";s:2:"Ѝ";s:4:"Ў";s:2:"Ў";s:4:"Й";s:2:"Й";s:4:"й";s:2:"й";s:4:"ѐ";s:2:"ѐ";s:4:"ё";s:2:"ё";s:4:"ѓ";s:2:"ѓ";s:4:"ї";s:2:"ї";s:4:"ќ";s:2:"ќ";s:4:"ѝ";s:2:"ѝ";s:4:"ў";s:2:"ў";s:4:"Ѷ";s:2:"Ѷ";s:4:"ѷ";s:2:"ѷ";s:4:"Ӂ";s:2:"Ӂ";s:4:"ӂ";s:2:"ӂ";s:4:"Ӑ";s:2:"Ӑ";s:4:"ӑ";s:2:"ӑ";s:4:"Ӓ";s:2:"Ӓ";s:4:"ӓ";s:2:"ӓ";s:4:"Ӗ";s:2:"Ӗ";s:4:"ӗ";s:2:"ӗ";s:4:"Ӛ";s:2:"Ӛ";s:4:"ӛ";s:2:"ӛ";s:4:"Ӝ";s:2:"Ӝ";s:4:"ӝ";s:2:"ӝ";s:4:"Ӟ";s:2:"Ӟ";s:4:"ӟ";s:2:"ӟ";s:4:"Ӣ";s:2:"Ӣ";s:4:"ӣ";s:2:"ӣ";s:4:"Ӥ";s:2:"Ӥ";s:4:"ӥ";s:2:"ӥ";s:4:"Ӧ";s:2:"Ӧ";s:4:"ӧ";s:2:"ӧ";s:4:"Ӫ";s:2:"Ӫ";s:4:"ӫ";s:2:"ӫ";s:4:"Ӭ";s:2:"Ӭ";s:4:"ӭ";s:2:"ӭ";s:4:"Ӯ";s:2:"Ӯ";s:4:"ӯ";s:2:"ӯ";s:4:"Ӱ";s:2:"Ӱ";s:4:"ӱ";s:2:"ӱ";s:4:"Ӳ";s:2:"Ӳ";s:4:"ӳ";s:2:"ӳ";s:4:"Ӵ";s:2:"Ӵ";s:4:"ӵ";s:2:"ӵ";s:4:"Ӹ";s:2:"Ӹ";s:4:"ӹ";s:2:"ӹ";s:4:"آ";s:2:"آ";s:4:"أ";s:2:"أ";s:4:"ؤ";s:2:"ؤ";s:4:"إ";s:2:"إ";s:4:"ئ";s:2:"ئ";s:4:"ۀ";s:2:"ۀ";s:4:"ۂ";s:2:"ۂ";s:4:"ۓ";s:2:"ۓ";s:6:"ऩ";s:3:"ऩ";s:6:"ऱ";s:3:"ऱ";s:6:"ऴ";s:3:"ऴ";s:6:"ো";s:3:"ো";s:6:"ৌ";s:3:"ৌ";s:6:"ୈ";s:3:"ୈ";s:6:"ୋ";s:3:"ୋ";s:6:"ୌ";s:3:"ୌ";s:6:"ஔ";s:3:"ஔ";s:6:"ொ";s:3:"ொ";s:6:"ோ";s:3:"ோ";s:6:"ௌ";s:3:"ௌ";s:6:"ై";s:3:"ై";s:6:"ೀ";s:3:"ೀ";s:6:"ೇ";s:3:"ೇ";s:6:"ೈ";s:3:"ೈ";s:6:"ೊ";s:3:"ೊ";s:6:"ೋ";s:3:"ೋ";s:6:"ൊ";s:3:"ൊ";s:6:"ോ";s:3:"ോ";s:6:"ൌ";s:3:"ൌ";s:6:"ේ";s:3:"ේ";s:6:"ො";s:3:"ො";s:6:"ෝ";s:3:"ෝ";s:6:"ෞ";s:3:"ෞ";s:6:"ཱི";s:3:"ཱི";s:6:"ཱུ";s:3:"ཱུ";s:6:"ཱྀ";s:3:"ཱྀ";s:6:"ဦ";s:3:"ဦ";s:6:"ᬆ";s:3:"ᬆ";s:6:"ᬈ";s:3:"ᬈ";s:6:"ᬊ";s:3:"ᬊ";s:6:"ᬌ";s:3:"ᬌ";s:6:"ᬎ";s:3:"ᬎ";s:6:"ᬒ";s:3:"ᬒ";s:6:"ᬻ";s:3:"ᬻ";s:6:"ᬽ";s:3:"ᬽ";s:6:"ᭀ";s:3:"ᭀ";s:6:"ᭁ";s:3:"ᭁ";s:6:"ᭃ";s:3:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:4:"Ḉ";s:3:"Ḉ";s:4:"ḉ";s:3:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:4:"Ḕ";s:3:"Ḕ";s:4:"ḕ";s:3:"ḕ";s:4:"Ḗ";s:3:"Ḗ";s:4:"ḗ";s:3:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:4:"Ḝ";s:3:"Ḝ";s:4:"ḝ";s:3:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:4:"Ḯ";s:3:"Ḯ";s:4:"ḯ";s:3:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:5:"Ḹ";s:3:"Ḹ";s:5:"ḹ";s:3:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:4:"Ṍ";s:3:"Ṍ";s:4:"ṍ";s:3:"ṍ";s:4:"Ṏ";s:3:"Ṏ";s:4:"ṏ";s:3:"ṏ";s:4:"Ṑ";s:3:"Ṑ";s:4:"ṑ";s:3:"ṑ";s:4:"Ṓ";s:3:"Ṓ";s:4:"ṓ";s:3:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:5:"Ṝ";s:3:"Ṝ";s:5:"ṝ";s:3:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:4:"Ṥ";s:3:"Ṥ";s:4:"ṥ";s:3:"ṥ";s:4:"Ṧ";s:3:"Ṧ";s:4:"ṧ";s:3:"ṧ";s:5:"Ṩ";s:3:"Ṩ";s:5:"ṩ";s:3:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:4:"Ṹ";s:3:"Ṹ";s:4:"ṹ";s:3:"ṹ";s:4:"Ṻ";s:3:"Ṻ";s:4:"ṻ";s:3:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:4:"ẛ";s:3:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:4:"Ấ";s:3:"Ấ";s:4:"ấ";s:3:"ấ";s:4:"Ầ";s:3:"Ầ";s:4:"ầ";s:3:"ầ";s:4:"Ẩ";s:3:"Ẩ";s:4:"ẩ";s:3:"ẩ";s:4:"Ẫ";s:3:"Ẫ";s:4:"ẫ";s:3:"ẫ";s:5:"Ậ";s:3:"Ậ";s:5:"ậ";s:3:"ậ";s:4:"Ắ";s:3:"Ắ";s:4:"ắ";s:3:"ắ";s:4:"Ằ";s:3:"Ằ";s:4:"ằ";s:3:"ằ";s:4:"Ẳ";s:3:"Ẳ";s:4:"ẳ";s:3:"ẳ";s:4:"Ẵ";s:3:"Ẵ";s:4:"ẵ";s:3:"ẵ";s:5:"Ặ";s:3:"Ặ";s:5:"ặ";s:3:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:4:"Ế";s:3:"Ế";s:4:"ế";s:3:"ế";s:4:"Ề";s:3:"Ề";s:4:"ề";s:3:"ề";s:4:"Ể";s:3:"Ể";s:4:"ể";s:3:"ể";s:4:"Ễ";s:3:"Ễ";s:4:"ễ";s:3:"ễ";s:5:"Ệ";s:3:"Ệ";s:5:"ệ";s:3:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:4:"Ố";s:3:"Ố";s:4:"ố";s:3:"ố";s:4:"Ồ";s:3:"Ồ";s:4:"ồ";s:3:"ồ";s:4:"Ổ";s:3:"Ổ";s:4:"ổ";s:3:"ổ";s:4:"Ỗ";s:3:"Ỗ";s:4:"ỗ";s:3:"ỗ";s:5:"Ộ";s:3:"Ộ";s:5:"ộ";s:3:"ộ";s:4:"Ớ";s:3:"Ớ";s:4:"ớ";s:3:"ớ";s:4:"Ờ";s:3:"Ờ";s:4:"ờ";s:3:"ờ";s:4:"Ở";s:3:"Ở";s:4:"ở";s:3:"ở";s:4:"Ỡ";s:3:"Ỡ";s:4:"ỡ";s:3:"ỡ";s:4:"Ợ";s:3:"Ợ";s:4:"ợ";s:3:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:4:"Ứ";s:3:"Ứ";s:4:"ứ";s:3:"ứ";s:4:"Ừ";s:3:"Ừ";s:4:"ừ";s:3:"ừ";s:4:"Ử";s:3:"Ử";s:4:"ử";s:3:"ử";s:4:"Ữ";s:3:"Ữ";s:4:"ữ";s:3:"ữ";s:4:"Ự";s:3:"Ự";s:4:"ự";s:3:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:4:"ἀ";s:3:"ἀ";s:4:"ἁ";s:3:"ἁ";s:5:"ἂ";s:3:"ἂ";s:5:"ἃ";s:3:"ἃ";s:5:"ἄ";s:3:"ἄ";s:5:"ἅ";s:3:"ἅ";s:5:"ἆ";s:3:"ἆ";s:5:"ἇ";s:3:"ἇ";s:4:"Ἀ";s:3:"Ἀ";s:4:"Ἁ";s:3:"Ἁ";s:5:"Ἂ";s:3:"Ἂ";s:5:"Ἃ";s:3:"Ἃ";s:5:"Ἄ";s:3:"Ἄ";s:5:"Ἅ";s:3:"Ἅ";s:5:"Ἆ";s:3:"Ἆ";s:5:"Ἇ";s:3:"Ἇ";s:4:"ἐ";s:3:"ἐ";s:4:"ἑ";s:3:"ἑ";s:5:"ἒ";s:3:"ἒ";s:5:"ἓ";s:3:"ἓ";s:5:"ἔ";s:3:"ἔ";s:5:"ἕ";s:3:"ἕ";s:4:"Ἐ";s:3:"Ἐ";s:4:"Ἑ";s:3:"Ἑ";s:5:"Ἒ";s:3:"Ἒ";s:5:"Ἓ";s:3:"Ἓ";s:5:"Ἔ";s:3:"Ἔ";s:5:"Ἕ";s:3:"Ἕ";s:4:"ἠ";s:3:"ἠ";s:4:"ἡ";s:3:"ἡ";s:5:"ἢ";s:3:"ἢ";s:5:"ἣ";s:3:"ἣ";s:5:"ἤ";s:3:"ἤ";s:5:"ἥ";s:3:"ἥ";s:5:"ἦ";s:3:"ἦ";s:5:"ἧ";s:3:"ἧ";s:4:"Ἠ";s:3:"Ἠ";s:4:"Ἡ";s:3:"Ἡ";s:5:"Ἢ";s:3:"Ἢ";s:5:"Ἣ";s:3:"Ἣ";s:5:"Ἤ";s:3:"Ἤ";s:5:"Ἥ";s:3:"Ἥ";s:5:"Ἦ";s:3:"Ἦ";s:5:"Ἧ";s:3:"Ἧ";s:4:"ἰ";s:3:"ἰ";s:4:"ἱ";s:3:"ἱ";s:5:"ἲ";s:3:"ἲ";s:5:"ἳ";s:3:"ἳ";s:5:"ἴ";s:3:"ἴ";s:5:"ἵ";s:3:"ἵ";s:5:"ἶ";s:3:"ἶ";s:5:"ἷ";s:3:"ἷ";s:4:"Ἰ";s:3:"Ἰ";s:4:"Ἱ";s:3:"Ἱ";s:5:"Ἲ";s:3:"Ἲ";s:5:"Ἳ";s:3:"Ἳ";s:5:"Ἴ";s:3:"Ἴ";s:5:"Ἵ";s:3:"Ἵ";s:5:"Ἶ";s:3:"Ἶ";s:5:"Ἷ";s:3:"Ἷ";s:4:"ὀ";s:3:"ὀ";s:4:"ὁ";s:3:"ὁ";s:5:"ὂ";s:3:"ὂ";s:5:"ὃ";s:3:"ὃ";s:5:"ὄ";s:3:"ὄ";s:5:"ὅ";s:3:"ὅ";s:4:"Ὀ";s:3:"Ὀ";s:4:"Ὁ";s:3:"Ὁ";s:5:"Ὂ";s:3:"Ὂ";s:5:"Ὃ";s:3:"Ὃ";s:5:"Ὄ";s:3:"Ὄ";s:5:"Ὅ";s:3:"Ὅ";s:4:"ὐ";s:3:"ὐ";s:4:"ὑ";s:3:"ὑ";s:5:"ὒ";s:3:"ὒ";s:5:"ὓ";s:3:"ὓ";s:5:"ὔ";s:3:"ὔ";s:5:"ὕ";s:3:"ὕ";s:5:"ὖ";s:3:"ὖ";s:5:"ὗ";s:3:"ὗ";s:4:"Ὑ";s:3:"Ὑ";s:5:"Ὓ";s:3:"Ὓ";s:5:"Ὕ";s:3:"Ὕ";s:5:"Ὗ";s:3:"Ὗ";s:4:"ὠ";s:3:"ὠ";s:4:"ὡ";s:3:"ὡ";s:5:"ὢ";s:3:"ὢ";s:5:"ὣ";s:3:"ὣ";s:5:"ὤ";s:3:"ὤ";s:5:"ὥ";s:3:"ὥ";s:5:"ὦ";s:3:"ὦ";s:5:"ὧ";s:3:"ὧ";s:4:"Ὠ";s:3:"Ὠ";s:4:"Ὡ";s:3:"Ὡ";s:5:"Ὢ";s:3:"Ὢ";s:5:"Ὣ";s:3:"Ὣ";s:5:"Ὤ";s:3:"Ὤ";s:5:"Ὥ";s:3:"Ὥ";s:5:"Ὦ";s:3:"Ὦ";s:5:"Ὧ";s:3:"Ὧ";s:4:"ὰ";s:3:"ὰ";s:2:"ά";s:3:"ά";s:4:"ὲ";s:3:"ὲ";s:2:"έ";s:3:"έ";s:4:"ὴ";s:3:"ὴ";s:2:"ή";s:3:"ή";s:4:"ὶ";s:3:"ὶ";s:2:"ί";s:3:"ί";s:4:"ὸ";s:3:"ὸ";s:2:"ό";s:3:"ό";s:4:"ὺ";s:3:"ὺ";s:2:"ύ";s:3:"ύ";s:4:"ὼ";s:3:"ὼ";s:2:"ώ";s:3:"ώ";s:5:"ᾀ";s:3:"ᾀ";s:5:"ᾁ";s:3:"ᾁ";s:5:"ᾂ";s:3:"ᾂ";s:5:"ᾃ";s:3:"ᾃ";s:5:"ᾄ";s:3:"ᾄ";s:5:"ᾅ";s:3:"ᾅ";s:5:"ᾆ";s:3:"ᾆ";s:5:"ᾇ";s:3:"ᾇ";s:5:"ᾈ";s:3:"ᾈ";s:5:"ᾉ";s:3:"ᾉ";s:5:"ᾊ";s:3:"ᾊ";s:5:"ᾋ";s:3:"ᾋ";s:5:"ᾌ";s:3:"ᾌ";s:5:"ᾍ";s:3:"ᾍ";s:5:"ᾎ";s:3:"ᾎ";s:5:"ᾏ";s:3:"ᾏ";s:5:"ᾐ";s:3:"ᾐ";s:5:"ᾑ";s:3:"ᾑ";s:5:"ᾒ";s:3:"ᾒ";s:5:"ᾓ";s:3:"ᾓ";s:5:"ᾔ";s:3:"ᾔ";s:5:"ᾕ";s:3:"ᾕ";s:5:"ᾖ";s:3:"ᾖ";s:5:"ᾗ";s:3:"ᾗ";s:5:"ᾘ";s:3:"ᾘ";s:5:"ᾙ";s:3:"ᾙ";s:5:"ᾚ";s:3:"ᾚ";s:5:"ᾛ";s:3:"ᾛ";s:5:"ᾜ";s:3:"ᾜ";s:5:"ᾝ";s:3:"ᾝ";s:5:"ᾞ";s:3:"ᾞ";s:5:"ᾟ";s:3:"ᾟ";s:5:"ᾠ";s:3:"ᾠ";s:5:"ᾡ";s:3:"ᾡ";s:5:"ᾢ";s:3:"ᾢ";s:5:"ᾣ";s:3:"ᾣ";s:5:"ᾤ";s:3:"ᾤ";s:5:"ᾥ";s:3:"ᾥ";s:5:"ᾦ";s:3:"ᾦ";s:5:"ᾧ";s:3:"ᾧ";s:5:"ᾨ";s:3:"ᾨ";s:5:"ᾩ";s:3:"ᾩ";s:5:"ᾪ";s:3:"ᾪ";s:5:"ᾫ";s:3:"ᾫ";s:5:"ᾬ";s:3:"ᾬ";s:5:"ᾭ";s:3:"ᾭ";s:5:"ᾮ";s:3:"ᾮ";s:5:"ᾯ";s:3:"ᾯ";s:4:"ᾰ";s:3:"ᾰ";s:4:"ᾱ";s:3:"ᾱ";s:5:"ᾲ";s:3:"ᾲ";s:4:"ᾳ";s:3:"ᾳ";s:4:"ᾴ";s:3:"ᾴ";s:4:"ᾶ";s:3:"ᾶ";s:5:"ᾷ";s:3:"ᾷ";s:4:"Ᾰ";s:3:"Ᾰ";s:4:"Ᾱ";s:3:"Ᾱ";s:4:"Ὰ";s:3:"Ὰ";s:2:"Ά";s:3:"Ά";s:4:"ᾼ";s:3:"ᾼ";s:2:"ι";s:3:"ι";s:4:"῁";s:3:"῁";s:5:"ῂ";s:3:"ῂ";s:4:"ῃ";s:3:"ῃ";s:4:"ῄ";s:3:"ῄ";s:4:"ῆ";s:3:"ῆ";s:5:"ῇ";s:3:"ῇ";s:4:"Ὲ";s:3:"Ὲ";s:2:"Έ";s:3:"Έ";s:4:"Ὴ";s:3:"Ὴ";s:2:"Ή";s:3:"Ή";s:4:"ῌ";s:3:"ῌ";s:5:"῍";s:3:"῍";s:5:"῎";s:3:"῎";s:5:"῏";s:3:"῏";s:4:"ῐ";s:3:"ῐ";s:4:"ῑ";s:3:"ῑ";s:4:"ῒ";s:3:"ῒ";s:2:"ΐ";s:3:"ΐ";s:4:"ῖ";s:3:"ῖ";s:4:"ῗ";s:3:"ῗ";s:4:"Ῐ";s:3:"Ῐ";s:4:"Ῑ";s:3:"Ῑ";s:4:"Ὶ";s:3:"Ὶ";s:2:"Ί";s:3:"Ί";s:5:"῝";s:3:"῝";s:5:"῞";s:3:"῞";s:5:"῟";s:3:"῟";s:4:"ῠ";s:3:"ῠ";s:4:"ῡ";s:3:"ῡ";s:4:"ῢ";s:3:"ῢ";s:2:"ΰ";s:3:"ΰ";s:4:"ῤ";s:3:"ῤ";s:4:"ῥ";s:3:"ῥ";s:4:"ῦ";s:3:"ῦ";s:4:"ῧ";s:3:"ῧ";s:4:"Ῠ";s:3:"Ῠ";s:4:"Ῡ";s:3:"Ῡ";s:4:"Ὺ";s:3:"Ὺ";s:2:"Ύ";s:3:"Ύ";s:4:"Ῥ";s:3:"Ῥ";s:4:"῭";s:3:"῭";s:2:"΅";s:3:"΅";s:1:"`";s:3:"`";s:5:"ῲ";s:3:"ῲ";s:4:"ῳ";s:3:"ῳ";s:4:"ῴ";s:3:"ῴ";s:4:"ῶ";s:3:"ῶ";s:5:"ῷ";s:3:"ῷ";s:4:"Ὸ";s:3:"Ὸ";s:2:"Ό";s:3:"Ό";s:4:"Ὼ";s:3:"Ὼ";s:2:"Ώ";s:3:"Ώ";s:4:"ῼ";s:3:"ῼ";s:2:"´";s:3:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:2:"Ω";s:3:"Ω";s:1:"K";s:3:"K";s:2:"Å";s:3:"Å";s:5:"↚";s:3:"↚";s:5:"↛";s:3:"↛";s:5:"↮";s:3:"↮";s:5:"⇍";s:3:"⇍";s:5:"⇎";s:3:"⇎";s:5:"⇏";s:3:"⇏";s:5:"∄";s:3:"∄";s:5:"∉";s:3:"∉";s:5:"∌";s:3:"∌";s:5:"∤";s:3:"∤";s:5:"∦";s:3:"∦";s:5:"≁";s:3:"≁";s:5:"≄";s:3:"≄";s:5:"≇";s:3:"≇";s:5:"≉";s:3:"≉";s:3:"≠";s:3:"≠";s:5:"≢";s:3:"≢";s:5:"≭";s:3:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:5:"≰";s:3:"≰";s:5:"≱";s:3:"≱";s:5:"≴";s:3:"≴";s:5:"≵";s:3:"≵";s:5:"≸";s:3:"≸";s:5:"≹";s:3:"≹";s:5:"⊀";s:3:"⊀";s:5:"⊁";s:3:"⊁";s:5:"⊄";s:3:"⊄";s:5:"⊅";s:3:"⊅";s:5:"⊈";s:3:"⊈";s:5:"⊉";s:3:"⊉";s:5:"⊬";s:3:"⊬";s:5:"⊭";s:3:"⊭";s:5:"⊮";s:3:"⊮";s:5:"⊯";s:3:"⊯";s:5:"⋠";s:3:"⋠";s:5:"⋡";s:3:"⋡";s:5:"⋢";s:3:"⋢";s:5:"⋣";s:3:"⋣";s:5:"⋪";s:3:"⋪";s:5:"⋫";s:3:"⋫";s:5:"⋬";s:3:"⋬";s:5:"⋭";s:3:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:6:"が";s:3:"が";s:6:"ぎ";s:3:"ぎ";s:6:"ぐ";s:3:"ぐ";s:6:"げ";s:3:"げ";s:6:"ご";s:3:"ご";s:6:"ざ";s:3:"ざ";s:6:"じ";s:3:"じ";s:6:"ず";s:3:"ず";s:6:"ぜ";s:3:"ぜ";s:6:"ぞ";s:3:"ぞ";s:6:"だ";s:3:"だ";s:6:"ぢ";s:3:"ぢ";s:6:"づ";s:3:"づ";s:6:"で";s:3:"で";s:6:"ど";s:3:"ど";s:6:"ば";s:3:"ば";s:6:"ぱ";s:3:"ぱ";s:6:"び";s:3:"び";s:6:"ぴ";s:3:"ぴ";s:6:"ぶ";s:3:"ぶ";s:6:"ぷ";s:3:"ぷ";s:6:"べ";s:3:"べ";s:6:"ぺ";s:3:"ぺ";s:6:"ぼ";s:3:"ぼ";s:6:"ぽ";s:3:"ぽ";s:6:"ゔ";s:3:"ゔ";s:6:"ゞ";s:3:"ゞ";s:6:"ガ";s:3:"ガ";s:6:"ギ";s:3:"ギ";s:6:"グ";s:3:"グ";s:6:"ゲ";s:3:"ゲ";s:6:"ゴ";s:3:"ゴ";s:6:"ザ";s:3:"ザ";s:6:"ジ";s:3:"ジ";s:6:"ズ";s:3:"ズ";s:6:"ゼ";s:3:"ゼ";s:6:"ゾ";s:3:"ゾ";s:6:"ダ";s:3:"ダ";s:6:"ヂ";s:3:"ヂ";s:6:"ヅ";s:3:"ヅ";s:6:"デ";s:3:"デ";s:6:"ド";s:3:"ド";s:6:"バ";s:3:"バ";s:6:"パ";s:3:"パ";s:6:"ビ";s:3:"ビ";s:6:"ピ";s:3:"ピ";s:6:"ブ";s:3:"ブ";s:6:"プ";s:3:"プ";s:6:"ベ";s:3:"ベ";s:6:"ペ";s:3:"ペ";s:6:"ボ";s:3:"ボ";s:6:"ポ";s:3:"ポ";s:6:"ヴ";s:3:"ヴ";s:6:"ヷ";s:3:"ヷ";s:6:"ヸ";s:3:"ヸ";s:6:"ヹ";s:3:"ヹ";s:6:"ヺ";s:3:"ヺ";s:6:"ヾ";s:3:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:4:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:4:"廊";s:3:"朗";s:4:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:4:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:4:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:4:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:4:"異";s:3:"北";s:4:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:4:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:4:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:4:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:4:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:4:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:4:"侮";s:3:"僧";s:4:"僧";s:3:"免";s:4:"免";s:3:"勉";s:4:"勉";s:3:"勤";s:4:"勤";s:3:"卑";s:4:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:4:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:4:"屮";s:3:"悔";s:4:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:4:"憎";s:3:"懲";s:4:"懲";s:3:"敏";s:4:"敏";s:3:"既";s:3:"既";s:3:"暑";s:4:"暑";s:3:"梅";s:4:"梅";s:3:"海";s:4:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:4:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:4:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:4:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"著";s:4:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:4:"𤋮";s:3:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:4:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:4:"勇";s:3:"勺";s:4:"勺";s:3:"啕";s:3:"啕";s:3:"喙";s:4:"喙";s:3:"嗢";s:3:"嗢";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:4:"慎";s:3:"愈";s:3:"愈";s:3:"慠";s:3:"慠";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"望";s:4:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"滛";s:3:"滛";s:3:"滋";s:4:"滋";s:3:"瀞";s:4:"瀞";s:3:"瞧";s:3:"瞧";s:3:"爵";s:4:"爵";s:3:"犯";s:3:"犯";s:3:"瑱";s:4:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"盛";s:3:"盛";s:3:"直";s:4:"直";s:3:"睊";s:4:"睊";s:3:"着";s:3:"着";s:3:"磌";s:4:"磌";s:3:"窱";s:3:"窱";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"缾";s:3:"缾";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:4:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"調";s:3:"調";s:3:"請";s:3:"請";s:3:"諭";s:4:"諭";s:3:"變";s:4:"變";s:3:"輸";s:4:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"韛";s:3:"韛";s:3:"頋";s:4:"頋";s:3:"鬒";s:4:"鬒";s:4:"𢡊";s:3:"𢡊";s:4:"𢡄";s:3:"𢡄";s:4:"𣏕";s:3:"𣏕";s:3:"㮝";s:4:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:4:"䀹";s:4:"𥉉";s:3:"𥉉";s:4:"𥳐";s:3:"𥳐";s:4:"𧻓";s:3:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:8:"𑂚";s:4:"𑂚";s:8:"𑂜";s:4:"𑂜";s:8:"𑂫";s:4:"𑂫";s:3:"丽";s:4:"丽";s:3:"丸";s:4:"丸";s:3:"乁";s:4:"乁";s:4:"𠄢";s:4:"𠄢";s:3:"你";s:4:"你";s:3:"侻";s:4:"侻";s:3:"倂";s:4:"倂";s:3:"偺";s:4:"偺";s:3:"備";s:4:"備";s:3:"像";s:4:"像";s:3:"㒞";s:4:"㒞";s:4:"𠘺";s:4:"𠘺";s:3:"兔";s:4:"兔";s:3:"兤";s:4:"兤";s:3:"具";s:4:"具";s:4:"𠔜";s:4:"𠔜";s:3:"㒹";s:4:"㒹";s:3:"內";s:4:"內";s:3:"再";s:4:"再";s:4:"𠕋";s:4:"𠕋";s:3:"冗";s:4:"冗";s:3:"冤";s:4:"冤";s:3:"仌";s:4:"仌";s:3:"冬";s:4:"冬";s:4:"𩇟";s:4:"𩇟";s:3:"凵";s:4:"凵";s:3:"刃";s:4:"刃";s:3:"㓟";s:4:"㓟";s:3:"刻";s:4:"刻";s:3:"剆";s:4:"剆";s:3:"割";s:4:"割";s:3:"剷";s:4:"剷";s:3:"㔕";s:4:"㔕";s:3:"包";s:4:"包";s:3:"匆";s:4:"匆";s:3:"卉";s:4:"卉";s:3:"博";s:4:"博";s:3:"即";s:4:"即";s:3:"卽";s:4:"卽";s:3:"卿";s:4:"卿";s:4:"𠨬";s:4:"𠨬";s:3:"灰";s:4:"灰";s:3:"及";s:4:"及";s:3:"叟";s:4:"叟";s:4:"𠭣";s:4:"𠭣";s:3:"叫";s:4:"叫";s:3:"叱";s:4:"叱";s:3:"吆";s:4:"吆";s:3:"咞";s:4:"咞";s:3:"吸";s:4:"吸";s:3:"呈";s:4:"呈";s:3:"周";s:4:"周";s:3:"咢";s:4:"咢";s:3:"哶";s:4:"哶";s:3:"唐";s:4:"唐";s:3:"啓";s:4:"啓";s:3:"啣";s:4:"啣";s:3:"善";s:4:"善";s:3:"喫";s:4:"喫";s:3:"喳";s:4:"喳";s:3:"嗂";s:4:"嗂";s:3:"圖";s:4:"圖";s:3:"圗";s:4:"圗";s:3:"噑";s:4:"噑";s:3:"噴";s:4:"噴";s:3:"壮";s:4:"壮";s:3:"城";s:4:"城";s:3:"埴";s:4:"埴";s:3:"堍";s:4:"堍";s:3:"型";s:4:"型";s:3:"堲";s:4:"堲";s:3:"報";s:4:"報";s:3:"墬";s:4:"墬";s:4:"𡓤";s:4:"𡓤";s:3:"売";s:4:"売";s:3:"壷";s:4:"壷";s:3:"夆";s:4:"夆";s:3:"多";s:4:"多";s:3:"夢";s:4:"夢";s:3:"奢";s:4:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:3:"姬";s:4:"姬";s:3:"娛";s:4:"娛";s:3:"娧";s:4:"娧";s:3:"姘";s:4:"姘";s:3:"婦";s:4:"婦";s:3:"㛮";s:4:"㛮";s:3:"㛼";s:4:"㛼";s:3:"嬈";s:4:"嬈";s:3:"嬾";s:4:"嬾";s:4:"𡧈";s:4:"𡧈";s:3:"寃";s:4:"寃";s:3:"寘";s:4:"寘";s:3:"寳";s:4:"寳";s:4:"𡬘";s:4:"𡬘";s:3:"寿";s:4:"寿";s:3:"将";s:4:"将";s:3:"当";s:4:"当";s:3:"尢";s:4:"尢";s:3:"㞁";s:4:"㞁";s:3:"屠";s:4:"屠";s:3:"峀";s:4:"峀";s:3:"岍";s:4:"岍";s:4:"𡷤";s:4:"𡷤";s:3:"嵃";s:4:"嵃";s:4:"𡷦";s:4:"𡷦";s:3:"嵮";s:4:"嵮";s:3:"嵫";s:4:"嵫";s:3:"嵼";s:4:"嵼";s:3:"巡";s:4:"巡";s:3:"巢";s:4:"巢";s:3:"㠯";s:4:"㠯";s:3:"巽";s:4:"巽";s:3:"帨";s:4:"帨";s:3:"帽";s:4:"帽";s:3:"幩";s:4:"幩";s:3:"㡢";s:4:"㡢";s:4:"𢆃";s:4:"𢆃";s:3:"㡼";s:4:"㡼";s:3:"庰";s:4:"庰";s:3:"庳";s:4:"庳";s:3:"庶";s:4:"庶";s:4:"𪎒";s:4:"𪎒";s:3:"廾";s:4:"廾";s:4:"𢌱";s:4:"𢌱";s:3:"舁";s:4:"舁";s:3:"弢";s:4:"弢";s:3:"㣇";s:4:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:3:"形";s:4:"形";s:3:"彫";s:4:"彫";s:3:"㣣";s:4:"㣣";s:3:"徚";s:4:"徚";s:3:"忍";s:4:"忍";s:3:"志";s:4:"志";s:3:"忹";s:4:"忹";s:3:"悁";s:4:"悁";s:3:"㤺";s:4:"㤺";s:3:"㤜";s:4:"㤜";s:4:"𢛔";s:4:"𢛔";s:3:"惇";s:4:"惇";s:3:"慈";s:4:"慈";s:3:"慌";s:4:"慌";s:3:"慺";s:4:"慺";s:3:"憲";s:4:"憲";s:3:"憤";s:4:"憤";s:3:"憯";s:4:"憯";s:3:"懞";s:4:"懞";s:3:"成";s:4:"成";s:3:"戛";s:4:"戛";s:3:"扝";s:4:"扝";s:3:"抱";s:4:"抱";s:3:"拔";s:4:"拔";s:3:"捐";s:4:"捐";s:4:"𢬌";s:4:"𢬌";s:3:"挽";s:4:"挽";s:3:"拼";s:4:"拼";s:3:"捨";s:4:"捨";s:3:"掃";s:4:"掃";s:3:"揤";s:4:"揤";s:4:"𢯱";s:4:"𢯱";s:3:"搢";s:4:"搢";s:3:"揅";s:4:"揅";s:3:"掩";s:4:"掩";s:3:"㨮";s:4:"㨮";s:3:"摩";s:4:"摩";s:3:"摾";s:4:"摾";s:3:"撝";s:4:"撝";s:3:"摷";s:4:"摷";s:3:"㩬";s:4:"㩬";s:3:"敬";s:4:"敬";s:4:"𣀊";s:4:"𣀊";s:3:"旣";s:4:"旣";s:3:"書";s:4:"書";s:3:"晉";s:4:"晉";s:3:"㬙";s:4:"㬙";s:3:"㬈";s:4:"㬈";s:3:"㫤";s:4:"㫤";s:3:"冒";s:4:"冒";s:3:"冕";s:4:"冕";s:3:"最";s:4:"最";s:3:"暜";s:4:"暜";s:3:"肭";s:4:"肭";s:3:"䏙";s:4:"䏙";s:3:"朡";s:4:"朡";s:3:"杞";s:4:"杞";s:3:"杓";s:4:"杓";s:4:"𣏃";s:4:"𣏃";s:3:"㭉";s:4:"㭉";s:3:"柺";s:4:"柺";s:3:"枅";s:4:"枅";s:3:"桒";s:4:"桒";s:4:"𣑭";s:4:"𣑭";s:3:"梎";s:4:"梎";s:3:"栟";s:4:"栟";s:3:"椔";s:4:"椔";s:3:"楂";s:4:"楂";s:3:"榣";s:4:"榣";s:3:"槪";s:4:"槪";s:3:"檨";s:4:"檨";s:4:"𣚣";s:4:"𣚣";s:3:"櫛";s:4:"櫛";s:3:"㰘";s:4:"㰘";s:3:"次";s:4:"次";s:4:"𣢧";s:4:"𣢧";s:3:"歔";s:4:"歔";s:3:"㱎";s:4:"㱎";s:3:"歲";s:4:"歲";s:3:"殟";s:4:"殟";s:3:"殻";s:4:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:3:"汎";s:4:"汎";s:4:"𣲼";s:4:"𣲼";s:3:"沿";s:4:"沿";s:3:"泍";s:4:"泍";s:3:"汧";s:4:"汧";s:3:"洖";s:4:"洖";s:3:"派";s:4:"派";s:3:"浩";s:4:"浩";s:3:"浸";s:4:"浸";s:3:"涅";s:4:"涅";s:4:"𣴞";s:4:"𣴞";s:3:"洴";s:4:"洴";s:3:"港";s:4:"港";s:3:"湮";s:4:"湮";s:3:"㴳";s:4:"㴳";s:3:"滇";s:4:"滇";s:4:"𣻑";s:4:"𣻑";s:3:"淹";s:4:"淹";s:3:"潮";s:4:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:3:"濆";s:4:"濆";s:3:"瀹";s:4:"瀹";s:3:"瀛";s:4:"瀛";s:3:"㶖";s:4:"㶖";s:3:"灊";s:4:"灊";s:3:"災";s:4:"災";s:3:"灷";s:4:"灷";s:3:"炭";s:4:"炭";s:4:"𠔥";s:4:"𠔥";s:3:"煅";s:4:"煅";s:4:"𤉣";s:4:"𤉣";s:3:"熜";s:4:"熜";s:4:"𤎫";s:4:"𤎫";s:3:"爨";s:4:"爨";s:3:"牐";s:4:"牐";s:4:"𤘈";s:4:"𤘈";s:3:"犀";s:4:"犀";s:3:"犕";s:4:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:3:"獺";s:4:"獺";s:3:"王";s:4:"王";s:3:"㺬";s:4:"㺬";s:3:"玥";s:4:"玥";s:3:"㺸";s:4:"㺸";s:3:"瑇";s:4:"瑇";s:3:"瑜";s:4:"瑜";s:3:"璅";s:4:"璅";s:3:"瓊";s:4:"瓊";s:3:"㼛";s:4:"㼛";s:3:"甤";s:4:"甤";s:4:"𤰶";s:4:"𤰶";s:3:"甾";s:4:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"𢆟";s:4:"𢆟";s:3:"瘐";s:4:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:3:"㿼";s:4:"㿼";s:3:"䀈";s:4:"䀈";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:3:"眞";s:4:"眞";s:3:"真";s:4:"真";s:3:"瞋";s:4:"瞋";s:3:"䁆";s:4:"䁆";s:3:"䂖";s:4:"䂖";s:4:"𥐝";s:4:"𥐝";s:3:"硎";s:4:"硎";s:3:"䃣";s:4:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:3:"秫";s:4:"秫";s:3:"䄯";s:4:"䄯";s:3:"穊";s:4:"穊";s:3:"穏";s:4:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:3:"竮";s:4:"竮";s:3:"䈂";s:4:"䈂";s:4:"𥮫";s:4:"𥮫";s:3:"篆";s:4:"篆";s:3:"築";s:4:"築";s:3:"䈧";s:4:"䈧";s:4:"𥲀";s:4:"𥲀";s:3:"糒";s:4:"糒";s:3:"䊠";s:4:"䊠";s:3:"糨";s:4:"糨";s:3:"糣";s:4:"糣";s:3:"紀";s:4:"紀";s:4:"𥾆";s:4:"𥾆";s:3:"絣";s:4:"絣";s:3:"䌁";s:4:"䌁";s:3:"緇";s:4:"緇";s:3:"縂";s:4:"縂";s:3:"繅";s:4:"繅";s:3:"䌴";s:4:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:3:"䍙";s:4:"䍙";s:4:"𦋙";s:4:"𦋙";s:3:"罺";s:4:"罺";s:4:"𦌾";s:4:"𦌾";s:3:"羕";s:4:"羕";s:3:"翺";s:4:"翺";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:3:"聠";s:4:"聠";s:4:"𦖨";s:4:"𦖨";s:3:"聰";s:4:"聰";s:4:"𣍟";s:4:"𣍟";s:3:"䏕";s:4:"䏕";s:3:"育";s:4:"育";s:3:"脃";s:4:"脃";s:3:"䐋";s:4:"䐋";s:3:"脾";s:4:"脾";s:3:"媵";s:4:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:3:"舄";s:4:"舄";s:3:"辞";s:4:"辞";s:3:"䑫";s:4:"䑫";s:3:"芑";s:4:"芑";s:3:"芋";s:4:"芋";s:3:"芝";s:4:"芝";s:3:"劳";s:4:"劳";s:3:"花";s:4:"花";s:3:"芳";s:4:"芳";s:3:"芽";s:4:"芽";s:3:"苦";s:4:"苦";s:4:"𦬼";s:4:"𦬼";s:3:"茝";s:4:"茝";s:3:"荣";s:4:"荣";s:3:"莭";s:4:"莭";s:3:"茣";s:4:"茣";s:3:"莽";s:4:"莽";s:3:"菧";s:4:"菧";s:3:"荓";s:4:"荓";s:3:"菊";s:4:"菊";s:3:"菌";s:4:"菌";s:3:"菜";s:4:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:3:"䔫";s:4:"䔫";s:3:"蓱";s:4:"蓱";s:3:"蓳";s:4:"蓳";s:3:"蔖";s:4:"蔖";s:4:"𧏊";s:4:"𧏊";s:3:"蕤";s:4:"蕤";s:4:"𦼬";s:4:"𦼬";s:3:"䕝";s:4:"䕝";s:3:"䕡";s:4:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:3:"䕫";s:4:"䕫";s:3:"虐";s:4:"虐";s:3:"虧";s:4:"虧";s:3:"虩";s:4:"虩";s:3:"蚩";s:4:"蚩";s:3:"蚈";s:4:"蚈";s:3:"蜎";s:4:"蜎";s:3:"蛢";s:4:"蛢";s:3:"蜨";s:4:"蜨";s:3:"蝫";s:4:"蝫";s:3:"螆";s:4:"螆";s:3:"䗗";s:4:"䗗";s:3:"蟡";s:4:"蟡";s:3:"蠁";s:4:"蠁";s:3:"䗹";s:4:"䗹";s:3:"衠";s:4:"衠";s:3:"衣";s:4:"衣";s:4:"𧙧";s:4:"𧙧";s:3:"裗";s:4:"裗";s:3:"裞";s:4:"裞";s:3:"䘵";s:4:"䘵";s:3:"裺";s:4:"裺";s:3:"㒻";s:4:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:3:"䚾";s:4:"䚾";s:3:"䛇";s:4:"䛇";s:3:"誠";s:4:"誠";s:3:"豕";s:4:"豕";s:4:"𧲨";s:4:"𧲨";s:3:"貫";s:4:"貫";s:3:"賁";s:4:"賁";s:3:"贛";s:4:"贛";s:3:"起";s:4:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:3:"跋";s:4:"跋";s:3:"趼";s:4:"趼";s:3:"跰";s:4:"跰";s:4:"𠣞";s:4:"𠣞";s:3:"軔";s:4:"軔";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:3:"邔";s:4:"邔";s:3:"郱";s:4:"郱";s:3:"鄑";s:4:"鄑";s:4:"𨜮";s:4:"𨜮";s:3:"鄛";s:4:"鄛";s:3:"鈸";s:4:"鈸";s:3:"鋗";s:4:"鋗";s:3:"鋘";s:4:"鋘";s:3:"鉼";s:4:"鉼";s:3:"鏹";s:4:"鏹";s:3:"鐕";s:4:"鐕";s:4:"𨯺";s:4:"𨯺";s:3:"開";s:4:"開";s:3:"䦕";s:4:"䦕";s:3:"閷";s:4:"閷";s:4:"𨵷";s:4:"𨵷";s:3:"䧦";s:4:"䧦";s:3:"雃";s:4:"雃";s:3:"嶲";s:4:"嶲";s:3:"霣";s:4:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:3:"䩮";s:4:"䩮";s:3:"䩶";s:4:"䩶";s:3:"韠";s:4:"韠";s:4:"𩐊";s:4:"𩐊";s:3:"䪲";s:4:"䪲";s:4:"𩒖";s:4:"𩒖";s:3:"頩";s:4:"頩";s:4:"𩖶";s:4:"𩖶";s:3:"飢";s:4:"飢";s:3:"䬳";s:4:"䬳";s:3:"餩";s:4:"餩";s:3:"馧";s:4:"馧";s:3:"駂";s:4:"駂";s:3:"駾";s:4:"駾";s:3:"䯎";s:4:"䯎";s:4:"𩬰";s:4:"𩬰";s:3:"鱀";s:4:"鱀";s:3:"鳽";s:4:"鳽";s:3:"䳎";s:4:"䳎";s:3:"䳭";s:4:"䳭";s:3:"鵧";s:4:"鵧";s:4:"𪃎";s:4:"𪃎";s:3:"䳸";s:4:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:3:"麻";s:4:"麻";s:3:"䵖";s:4:"䵖";s:3:"黹";s:4:"黹";s:3:"黾";s:4:"黾";s:3:"鼅";s:4:"鼅";s:3:"鼏";s:4:"鼏";s:3:"鼖";s:4:"鼖";s:3:"鼻";s:4:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
+UtfNormal\Validator::$utfCanonicalDecomp = unserialize( 'a:2049:{s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:";";s:1:";";s:2:"΅";s:4:"΅";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϓ";s:4:"ϓ";s:2:"ϔ";s:4:"ϔ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẛ";s:4:"ẛ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"ι";s:2:"ι";s:3:"῁";s:4:"῁";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:"῍";s:3:"῎";s:5:"῎";s:3:"῏";s:5:"῏";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:"῝";s:3:"῞";s:5:"῞";s:3:"῟";s:5:"῟";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:4:"῭";s:3:"΅";s:4:"΅";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:2:"´";s:3:" ";s:3:" ";s:3:" ";s:3:" ";s:3:"Ω";s:2:"Ω";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"⫝̸";s:5:"⫝̸";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"ゞ";s:6:"ゞ";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:4:"𑂚";s:8:"𑂚";s:4:"𑂜";s:8:"𑂜";s:4:"𑂫";s:8:"𑂫";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
+UtfNormal\Validator::$utfCheckNFC = unserialize( 'a:1221:{s:2:"̀";s:1:"N";s:2:"́";s:1:"N";s:2:"̓";s:1:"N";s:2:"̈́";s:1:"N";s:2:"ʹ";s:1:"N";s:2:";";s:1:"N";s:2:"·";s:1:"N";s:3:"क़";s:1:"N";s:3:"ख़";s:1:"N";s:3:"ग़";s:1:"N";s:3:"ज़";s:1:"N";s:3:"ड़";s:1:"N";s:3:"ढ़";s:1:"N";s:3:"फ़";s:1:"N";s:3:"य़";s:1:"N";s:3:"ড়";s:1:"N";s:3:"ঢ়";s:1:"N";s:3:"য়";s:1:"N";s:3:"ਲ਼";s:1:"N";s:3:"ਸ਼";s:1:"N";s:3:"ਖ਼";s:1:"N";s:3:"ਗ਼";s:1:"N";s:3:"ਜ਼";s:1:"N";s:3:"ਫ਼";s:1:"N";s:3:"ଡ଼";s:1:"N";s:3:"ଢ଼";s:1:"N";s:3:"གྷ";s:1:"N";s:3:"ཌྷ";s:1:"N";s:3:"དྷ";s:1:"N";s:3:"བྷ";s:1:"N";s:3:"ཛྷ";s:1:"N";s:3:"ཀྵ";s:1:"N";s:3:"ཱི";s:1:"N";s:3:"ཱུ";s:1:"N";s:3:"ྲྀ";s:1:"N";s:3:"ླྀ";s:1:"N";s:3:"ཱྀ";s:1:"N";s:3:"ྒྷ";s:1:"N";s:3:"ྜྷ";s:1:"N";s:3:"ྡྷ";s:1:"N";s:3:"ྦྷ";s:1:"N";s:3:"ྫྷ";s:1:"N";s:3:"ྐྵ";s:1:"N";s:3:"ά";s:1:"N";s:3:"έ";s:1:"N";s:3:"ή";s:1:"N";s:3:"ί";s:1:"N";s:3:"ό";s:1:"N";s:3:"ύ";s:1:"N";s:3:"ώ";s:1:"N";s:3:"Ά";s:1:"N";s:3:"ι";s:1:"N";s:3:"Έ";s:1:"N";s:3:"Ή";s:1:"N";s:3:"ΐ";s:1:"N";s:3:"Ί";s:1:"N";s:3:"ΰ";s:1:"N";s:3:"Ύ";s:1:"N";s:3:"΅";s:1:"N";s:3:"`";s:1:"N";s:3:"Ό";s:1:"N";s:3:"Ώ";s:1:"N";s:3:"´";s:1:"N";s:3:" ";s:1:"N";s:3:" ";s:1:"N";s:3:"Ω";s:1:"N";s:3:"K";s:1:"N";s:3:"Å";s:1:"N";s:3:"〈";s:1:"N";s:3:"〉";s:1:"N";s:3:"⫝̸";s:1:"N";s:3:"豈";s:1:"N";s:3:"更";s:1:"N";s:3:"車";s:1:"N";s:3:"賈";s:1:"N";s:3:"滑";s:1:"N";s:3:"串";s:1:"N";s:3:"句";s:1:"N";s:3:"龜";s:1:"N";s:3:"龜";s:1:"N";s:3:"契";s:1:"N";s:3:"金";s:1:"N";s:3:"喇";s:1:"N";s:3:"奈";s:1:"N";s:3:"懶";s:1:"N";s:3:"癩";s:1:"N";s:3:"羅";s:1:"N";s:3:"蘿";s:1:"N";s:3:"螺";s:1:"N";s:3:"裸";s:1:"N";s:3:"邏";s:1:"N";s:3:"樂";s:1:"N";s:3:"洛";s:1:"N";s:3:"烙";s:1:"N";s:3:"珞";s:1:"N";s:3:"落";s:1:"N";s:3:"酪";s:1:"N";s:3:"駱";s:1:"N";s:3:"亂";s:1:"N";s:3:"卵";s:1:"N";s:3:"欄";s:1:"N";s:3:"爛";s:1:"N";s:3:"蘭";s:1:"N";s:3:"鸞";s:1:"N";s:3:"嵐";s:1:"N";s:3:"濫";s:1:"N";s:3:"藍";s:1:"N";s:3:"襤";s:1:"N";s:3:"拉";s:1:"N";s:3:"臘";s:1:"N";s:3:"蠟";s:1:"N";s:3:"廊";s:1:"N";s:3:"朗";s:1:"N";s:3:"浪";s:1:"N";s:3:"狼";s:1:"N";s:3:"郎";s:1:"N";s:3:"來";s:1:"N";s:3:"冷";s:1:"N";s:3:"勞";s:1:"N";s:3:"擄";s:1:"N";s:3:"櫓";s:1:"N";s:3:"爐";s:1:"N";s:3:"盧";s:1:"N";s:3:"老";s:1:"N";s:3:"蘆";s:1:"N";s:3:"虜";s:1:"N";s:3:"路";s:1:"N";s:3:"露";s:1:"N";s:3:"魯";s:1:"N";s:3:"鷺";s:1:"N";s:3:"碌";s:1:"N";s:3:"祿";s:1:"N";s:3:"綠";s:1:"N";s:3:"菉";s:1:"N";s:3:"錄";s:1:"N";s:3:"鹿";s:1:"N";s:3:"論";s:1:"N";s:3:"壟";s:1:"N";s:3:"弄";s:1:"N";s:3:"籠";s:1:"N";s:3:"聾";s:1:"N";s:3:"牢";s:1:"N";s:3:"磊";s:1:"N";s:3:"賂";s:1:"N";s:3:"雷";s:1:"N";s:3:"壘";s:1:"N";s:3:"屢";s:1:"N";s:3:"樓";s:1:"N";s:3:"淚";s:1:"N";s:3:"漏";s:1:"N";s:3:"累";s:1:"N";s:3:"縷";s:1:"N";s:3:"陋";s:1:"N";s:3:"勒";s:1:"N";s:3:"肋";s:1:"N";s:3:"凜";s:1:"N";s:3:"凌";s:1:"N";s:3:"稜";s:1:"N";s:3:"綾";s:1:"N";s:3:"菱";s:1:"N";s:3:"陵";s:1:"N";s:3:"讀";s:1:"N";s:3:"拏";s:1:"N";s:3:"樂";s:1:"N";s:3:"諾";s:1:"N";s:3:"丹";s:1:"N";s:3:"寧";s:1:"N";s:3:"怒";s:1:"N";s:3:"率";s:1:"N";s:3:"異";s:1:"N";s:3:"北";s:1:"N";s:3:"磻";s:1:"N";s:3:"便";s:1:"N";s:3:"復";s:1:"N";s:3:"不";s:1:"N";s:3:"泌";s:1:"N";s:3:"數";s:1:"N";s:3:"索";s:1:"N";s:3:"參";s:1:"N";s:3:"塞";s:1:"N";s:3:"省";s:1:"N";s:3:"葉";s:1:"N";s:3:"說";s:1:"N";s:3:"殺";s:1:"N";s:3:"辰";s:1:"N";s:3:"沈";s:1:"N";s:3:"拾";s:1:"N";s:3:"若";s:1:"N";s:3:"掠";s:1:"N";s:3:"略";s:1:"N";s:3:"亮";s:1:"N";s:3:"兩";s:1:"N";s:3:"凉";s:1:"N";s:3:"梁";s:1:"N";s:3:"糧";s:1:"N";s:3:"良";s:1:"N";s:3:"諒";s:1:"N";s:3:"量";s:1:"N";s:3:"勵";s:1:"N";s:3:"呂";s:1:"N";s:3:"女";s:1:"N";s:3:"廬";s:1:"N";s:3:"旅";s:1:"N";s:3:"濾";s:1:"N";s:3:"礪";s:1:"N";s:3:"閭";s:1:"N";s:3:"驪";s:1:"N";s:3:"麗";s:1:"N";s:3:"黎";s:1:"N";s:3:"力";s:1:"N";s:3:"曆";s:1:"N";s:3:"歷";s:1:"N";s:3:"轢";s:1:"N";s:3:"年";s:1:"N";s:3:"憐";s:1:"N";s:3:"戀";s:1:"N";s:3:"撚";s:1:"N";s:3:"漣";s:1:"N";s:3:"煉";s:1:"N";s:3:"璉";s:1:"N";s:3:"秊";s:1:"N";s:3:"練";s:1:"N";s:3:"聯";s:1:"N";s:3:"輦";s:1:"N";s:3:"蓮";s:1:"N";s:3:"連";s:1:"N";s:3:"鍊";s:1:"N";s:3:"列";s:1:"N";s:3:"劣";s:1:"N";s:3:"咽";s:1:"N";s:3:"烈";s:1:"N";s:3:"裂";s:1:"N";s:3:"說";s:1:"N";s:3:"廉";s:1:"N";s:3:"念";s:1:"N";s:3:"捻";s:1:"N";s:3:"殮";s:1:"N";s:3:"簾";s:1:"N";s:3:"獵";s:1:"N";s:3:"令";s:1:"N";s:3:"囹";s:1:"N";s:3:"寧";s:1:"N";s:3:"嶺";s:1:"N";s:3:"怜";s:1:"N";s:3:"玲";s:1:"N";s:3:"瑩";s:1:"N";s:3:"羚";s:1:"N";s:3:"聆";s:1:"N";s:3:"鈴";s:1:"N";s:3:"零";s:1:"N";s:3:"靈";s:1:"N";s:3:"領";s:1:"N";s:3:"例";s:1:"N";s:3:"禮";s:1:"N";s:3:"醴";s:1:"N";s:3:"隸";s:1:"N";s:3:"惡";s:1:"N";s:3:"了";s:1:"N";s:3:"僚";s:1:"N";s:3:"寮";s:1:"N";s:3:"尿";s:1:"N";s:3:"料";s:1:"N";s:3:"樂";s:1:"N";s:3:"燎";s:1:"N";s:3:"療";s:1:"N";s:3:"蓼";s:1:"N";s:3:"遼";s:1:"N";s:3:"龍";s:1:"N";s:3:"暈";s:1:"N";s:3:"阮";s:1:"N";s:3:"劉";s:1:"N";s:3:"杻";s:1:"N";s:3:"柳";s:1:"N";s:3:"流";s:1:"N";s:3:"溜";s:1:"N";s:3:"琉";s:1:"N";s:3:"留";s:1:"N";s:3:"硫";s:1:"N";s:3:"紐";s:1:"N";s:3:"類";s:1:"N";s:3:"六";s:1:"N";s:3:"戮";s:1:"N";s:3:"陸";s:1:"N";s:3:"倫";s:1:"N";s:3:"崙";s:1:"N";s:3:"淪";s:1:"N";s:3:"輪";s:1:"N";s:3:"律";s:1:"N";s:3:"慄";s:1:"N";s:3:"栗";s:1:"N";s:3:"率";s:1:"N";s:3:"隆";s:1:"N";s:3:"利";s:1:"N";s:3:"吏";s:1:"N";s:3:"履";s:1:"N";s:3:"易";s:1:"N";s:3:"李";s:1:"N";s:3:"梨";s:1:"N";s:3:"泥";s:1:"N";s:3:"理";s:1:"N";s:3:"痢";s:1:"N";s:3:"罹";s:1:"N";s:3:"裏";s:1:"N";s:3:"裡";s:1:"N";s:3:"里";s:1:"N";s:3:"離";s:1:"N";s:3:"匿";s:1:"N";s:3:"溺";s:1:"N";s:3:"吝";s:1:"N";s:3:"燐";s:1:"N";s:3:"璘";s:1:"N";s:3:"藺";s:1:"N";s:3:"隣";s:1:"N";s:3:"鱗";s:1:"N";s:3:"麟";s:1:"N";s:3:"林";s:1:"N";s:3:"淋";s:1:"N";s:3:"臨";s:1:"N";s:3:"立";s:1:"N";s:3:"笠";s:1:"N";s:3:"粒";s:1:"N";s:3:"狀";s:1:"N";s:3:"炙";s:1:"N";s:3:"識";s:1:"N";s:3:"什";s:1:"N";s:3:"茶";s:1:"N";s:3:"刺";s:1:"N";s:3:"切";s:1:"N";s:3:"度";s:1:"N";s:3:"拓";s:1:"N";s:3:"糖";s:1:"N";s:3:"宅";s:1:"N";s:3:"洞";s:1:"N";s:3:"暴";s:1:"N";s:3:"輻";s:1:"N";s:3:"行";s:1:"N";s:3:"降";s:1:"N";s:3:"見";s:1:"N";s:3:"廓";s:1:"N";s:3:"兀";s:1:"N";s:3:"嗀";s:1:"N";s:3:"塚";s:1:"N";s:3:"晴";s:1:"N";s:3:"凞";s:1:"N";s:3:"猪";s:1:"N";s:3:"益";s:1:"N";s:3:"礼";s:1:"N";s:3:"神";s:1:"N";s:3:"祥";s:1:"N";s:3:"福";s:1:"N";s:3:"靖";s:1:"N";s:3:"精";s:1:"N";s:3:"羽";s:1:"N";s:3:"蘒";s:1:"N";s:3:"諸";s:1:"N";s:3:"逸";s:1:"N";s:3:"都";s:1:"N";s:3:"飯";s:1:"N";s:3:"飼";s:1:"N";s:3:"館";s:1:"N";s:3:"鶴";s:1:"N";s:3:"侮";s:1:"N";s:3:"僧";s:1:"N";s:3:"免";s:1:"N";s:3:"勉";s:1:"N";s:3:"勤";s:1:"N";s:3:"卑";s:1:"N";s:3:"喝";s:1:"N";s:3:"嘆";s:1:"N";s:3:"器";s:1:"N";s:3:"塀";s:1:"N";s:3:"墨";s:1:"N";s:3:"層";s:1:"N";s:3:"屮";s:1:"N";s:3:"悔";s:1:"N";s:3:"慨";s:1:"N";s:3:"憎";s:1:"N";s:3:"懲";s:1:"N";s:3:"敏";s:1:"N";s:3:"既";s:1:"N";s:3:"暑";s:1:"N";s:3:"梅";s:1:"N";s:3:"海";s:1:"N";s:3:"渚";s:1:"N";s:3:"漢";s:1:"N";s:3:"煮";s:1:"N";s:3:"爫";s:1:"N";s:3:"琢";s:1:"N";s:3:"碑";s:1:"N";s:3:"社";s:1:"N";s:3:"祉";s:1:"N";s:3:"祈";s:1:"N";s:3:"祐";s:1:"N";s:3:"祖";s:1:"N";s:3:"祝";s:1:"N";s:3:"禍";s:1:"N";s:3:"禎";s:1:"N";s:3:"穀";s:1:"N";s:3:"突";s:1:"N";s:3:"節";s:1:"N";s:3:"練";s:1:"N";s:3:"縉";s:1:"N";s:3:"繁";s:1:"N";s:3:"署";s:1:"N";s:3:"者";s:1:"N";s:3:"臭";s:1:"N";s:3:"艹";s:1:"N";s:3:"艹";s:1:"N";s:3:"著";s:1:"N";s:3:"褐";s:1:"N";s:3:"視";s:1:"N";s:3:"謁";s:1:"N";s:3:"謹";s:1:"N";s:3:"賓";s:1:"N";s:3:"贈";s:1:"N";s:3:"辶";s:1:"N";s:3:"逸";s:1:"N";s:3:"難";s:1:"N";s:3:"響";s:1:"N";s:3:"頻";s:1:"N";s:3:"恵";s:1:"N";s:3:"𤋮";s:1:"N";s:3:"舘";s:1:"N";s:3:"並";s:1:"N";s:3:"况";s:1:"N";s:3:"全";s:1:"N";s:3:"侀";s:1:"N";s:3:"充";s:1:"N";s:3:"冀";s:1:"N";s:3:"勇";s:1:"N";s:3:"勺";s:1:"N";s:3:"喝";s:1:"N";s:3:"啕";s:1:"N";s:3:"喙";s:1:"N";s:3:"嗢";s:1:"N";s:3:"塚";s:1:"N";s:3:"墳";s:1:"N";s:3:"奄";s:1:"N";s:3:"奔";s:1:"N";s:3:"婢";s:1:"N";s:3:"嬨";s:1:"N";s:3:"廒";s:1:"N";s:3:"廙";s:1:"N";s:3:"彩";s:1:"N";s:3:"徭";s:1:"N";s:3:"惘";s:1:"N";s:3:"慎";s:1:"N";s:3:"愈";s:1:"N";s:3:"憎";s:1:"N";s:3:"慠";s:1:"N";s:3:"懲";s:1:"N";s:3:"戴";s:1:"N";s:3:"揄";s:1:"N";s:3:"搜";s:1:"N";s:3:"摒";s:1:"N";s:3:"敖";s:1:"N";s:3:"晴";s:1:"N";s:3:"朗";s:1:"N";s:3:"望";s:1:"N";s:3:"杖";s:1:"N";s:3:"歹";s:1:"N";s:3:"殺";s:1:"N";s:3:"流";s:1:"N";s:3:"滛";s:1:"N";s:3:"滋";s:1:"N";s:3:"漢";s:1:"N";s:3:"瀞";s:1:"N";s:3:"煮";s:1:"N";s:3:"瞧";s:1:"N";s:3:"爵";s:1:"N";s:3:"犯";s:1:"N";s:3:"猪";s:1:"N";s:3:"瑱";s:1:"N";s:3:"甆";s:1:"N";s:3:"画";s:1:"N";s:3:"瘝";s:1:"N";s:3:"瘟";s:1:"N";s:3:"益";s:1:"N";s:3:"盛";s:1:"N";s:3:"直";s:1:"N";s:3:"睊";s:1:"N";s:3:"着";s:1:"N";s:3:"磌";s:1:"N";s:3:"窱";s:1:"N";s:3:"節";s:1:"N";s:3:"类";s:1:"N";s:3:"絛";s:1:"N";s:3:"練";s:1:"N";s:3:"缾";s:1:"N";s:3:"者";s:1:"N";s:3:"荒";s:1:"N";s:3:"華";s:1:"N";s:3:"蝹";s:1:"N";s:3:"襁";s:1:"N";s:3:"覆";s:1:"N";s:3:"視";s:1:"N";s:3:"調";s:1:"N";s:3:"諸";s:1:"N";s:3:"請";s:1:"N";s:3:"謁";s:1:"N";s:3:"諾";s:1:"N";s:3:"諭";s:1:"N";s:3:"謹";s:1:"N";s:3:"變";s:1:"N";s:3:"贈";s:1:"N";s:3:"輸";s:1:"N";s:3:"遲";s:1:"N";s:3:"醙";s:1:"N";s:3:"鉶";s:1:"N";s:3:"陼";s:1:"N";s:3:"難";s:1:"N";s:3:"靖";s:1:"N";s:3:"韛";s:1:"N";s:3:"響";s:1:"N";s:3:"頋";s:1:"N";s:3:"頻";s:1:"N";s:3:"鬒";s:1:"N";s:3:"龜";s:1:"N";s:3:"𢡊";s:1:"N";s:3:"𢡄";s:1:"N";s:3:"𣏕";s:1:"N";s:3:"㮝";s:1:"N";s:3:"䀘";s:1:"N";s:3:"䀹";s:1:"N";s:3:"𥉉";s:1:"N";s:3:"𥳐";s:1:"N";s:3:"𧻓";s:1:"N";s:3:"齃";s:1:"N";s:3:"龎";s:1:"N";s:3:"יִ";s:1:"N";s:3:"ײַ";s:1:"N";s:3:"שׁ";s:1:"N";s:3:"שׂ";s:1:"N";s:3:"שּׁ";s:1:"N";s:3:"שּׂ";s:1:"N";s:3:"אַ";s:1:"N";s:3:"אָ";s:1:"N";s:3:"אּ";s:1:"N";s:3:"בּ";s:1:"N";s:3:"גּ";s:1:"N";s:3:"דּ";s:1:"N";s:3:"הּ";s:1:"N";s:3:"וּ";s:1:"N";s:3:"זּ";s:1:"N";s:3:"טּ";s:1:"N";s:3:"יּ";s:1:"N";s:3:"ךּ";s:1:"N";s:3:"כּ";s:1:"N";s:3:"לּ";s:1:"N";s:3:"מּ";s:1:"N";s:3:"נּ";s:1:"N";s:3:"סּ";s:1:"N";s:3:"ףּ";s:1:"N";s:3:"פּ";s:1:"N";s:3:"צּ";s:1:"N";s:3:"קּ";s:1:"N";s:3:"רּ";s:1:"N";s:3:"שּ";s:1:"N";s:3:"תּ";s:1:"N";s:3:"וֹ";s:1:"N";s:3:"בֿ";s:1:"N";s:3:"כֿ";s:1:"N";s:3:"פֿ";s:1:"N";s:4:"𝅗𝅥";s:1:"N";s:4:"𝅘𝅥";s:1:"N";s:4:"𝅘𝅥𝅮";s:1:"N";s:4:"𝅘𝅥𝅯";s:1:"N";s:4:"𝅘𝅥𝅰";s:1:"N";s:4:"𝅘𝅥𝅱";s:1:"N";s:4:"𝅘𝅥𝅲";s:1:"N";s:4:"𝆹𝅥";s:1:"N";s:4:"𝆺𝅥";s:1:"N";s:4:"𝆹𝅥𝅮";s:1:"N";s:4:"𝆺𝅥𝅮";s:1:"N";s:4:"𝆹𝅥𝅯";s:1:"N";s:4:"𝆺𝅥𝅯";s:1:"N";s:4:"丽";s:1:"N";s:4:"丸";s:1:"N";s:4:"乁";s:1:"N";s:4:"𠄢";s:1:"N";s:4:"你";s:1:"N";s:4:"侮";s:1:"N";s:4:"侻";s:1:"N";s:4:"倂";s:1:"N";s:4:"偺";s:1:"N";s:4:"備";s:1:"N";s:4:"僧";s:1:"N";s:4:"像";s:1:"N";s:4:"㒞";s:1:"N";s:4:"𠘺";s:1:"N";s:4:"免";s:1:"N";s:4:"兔";s:1:"N";s:4:"兤";s:1:"N";s:4:"具";s:1:"N";s:4:"𠔜";s:1:"N";s:4:"㒹";s:1:"N";s:4:"內";s:1:"N";s:4:"再";s:1:"N";s:4:"𠕋";s:1:"N";s:4:"冗";s:1:"N";s:4:"冤";s:1:"N";s:4:"仌";s:1:"N";s:4:"冬";s:1:"N";s:4:"况";s:1:"N";s:4:"𩇟";s:1:"N";s:4:"凵";s:1:"N";s:4:"刃";s:1:"N";s:4:"㓟";s:1:"N";s:4:"刻";s:1:"N";s:4:"剆";s:1:"N";s:4:"割";s:1:"N";s:4:"剷";s:1:"N";s:4:"㔕";s:1:"N";s:4:"勇";s:1:"N";s:4:"勉";s:1:"N";s:4:"勤";s:1:"N";s:4:"勺";s:1:"N";s:4:"包";s:1:"N";s:4:"匆";s:1:"N";s:4:"北";s:1:"N";s:4:"卉";s:1:"N";s:4:"卑";s:1:"N";s:4:"博";s:1:"N";s:4:"即";s:1:"N";s:4:"卽";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"卿";s:1:"N";s:4:"𠨬";s:1:"N";s:4:"灰";s:1:"N";s:4:"及";s:1:"N";s:4:"叟";s:1:"N";s:4:"𠭣";s:1:"N";s:4:"叫";s:1:"N";s:4:"叱";s:1:"N";s:4:"吆";s:1:"N";s:4:"咞";s:1:"N";s:4:"吸";s:1:"N";s:4:"呈";s:1:"N";s:4:"周";s:1:"N";s:4:"咢";s:1:"N";s:4:"哶";s:1:"N";s:4:"唐";s:1:"N";s:4:"啓";s:1:"N";s:4:"啣";s:1:"N";s:4:"善";s:1:"N";s:4:"善";s:1:"N";s:4:"喙";s:1:"N";s:4:"喫";s:1:"N";s:4:"喳";s:1:"N";s:4:"嗂";s:1:"N";s:4:"圖";s:1:"N";s:4:"嘆";s:1:"N";s:4:"圗";s:1:"N";s:4:"噑";s:1:"N";s:4:"噴";s:1:"N";s:4:"切";s:1:"N";s:4:"壮";s:1:"N";s:4:"城";s:1:"N";s:4:"埴";s:1:"N";s:4:"堍";s:1:"N";s:4:"型";s:1:"N";s:4:"堲";s:1:"N";s:4:"報";s:1:"N";s:4:"墬";s:1:"N";s:4:"𡓤";s:1:"N";s:4:"売";s:1:"N";s:4:"壷";s:1:"N";s:4:"夆";s:1:"N";s:4:"多";s:1:"N";s:4:"夢";s:1:"N";s:4:"奢";s:1:"N";s:4:"𡚨";s:1:"N";s:4:"𡛪";s:1:"N";s:4:"姬";s:1:"N";s:4:"娛";s:1:"N";s:4:"娧";s:1:"N";s:4:"姘";s:1:"N";s:4:"婦";s:1:"N";s:4:"㛮";s:1:"N";s:4:"㛼";s:1:"N";s:4:"嬈";s:1:"N";s:4:"嬾";s:1:"N";s:4:"嬾";s:1:"N";s:4:"𡧈";s:1:"N";s:4:"寃";s:1:"N";s:4:"寘";s:1:"N";s:4:"寧";s:1:"N";s:4:"寳";s:1:"N";s:4:"𡬘";s:1:"N";s:4:"寿";s:1:"N";s:4:"将";s:1:"N";s:4:"当";s:1:"N";s:4:"尢";s:1:"N";s:4:"㞁";s:1:"N";s:4:"屠";s:1:"N";s:4:"屮";s:1:"N";s:4:"峀";s:1:"N";s:4:"岍";s:1:"N";s:4:"𡷤";s:1:"N";s:4:"嵃";s:1:"N";s:4:"𡷦";s:1:"N";s:4:"嵮";s:1:"N";s:4:"嵫";s:1:"N";s:4:"嵼";s:1:"N";s:4:"巡";s:1:"N";s:4:"巢";s:1:"N";s:4:"㠯";s:1:"N";s:4:"巽";s:1:"N";s:4:"帨";s:1:"N";s:4:"帽";s:1:"N";s:4:"幩";s:1:"N";s:4:"㡢";s:1:"N";s:4:"𢆃";s:1:"N";s:4:"㡼";s:1:"N";s:4:"庰";s:1:"N";s:4:"庳";s:1:"N";s:4:"庶";s:1:"N";s:4:"廊";s:1:"N";s:4:"𪎒";s:1:"N";s:4:"廾";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"𢌱";s:1:"N";s:4:"舁";s:1:"N";s:4:"弢";s:1:"N";s:4:"弢";s:1:"N";s:4:"㣇";s:1:"N";s:4:"𣊸";s:1:"N";s:4:"𦇚";s:1:"N";s:4:"形";s:1:"N";s:4:"彫";s:1:"N";s:4:"㣣";s:1:"N";s:4:"徚";s:1:"N";s:4:"忍";s:1:"N";s:4:"志";s:1:"N";s:4:"忹";s:1:"N";s:4:"悁";s:1:"N";s:4:"㤺";s:1:"N";s:4:"㤜";s:1:"N";s:4:"悔";s:1:"N";s:4:"𢛔";s:1:"N";s:4:"惇";s:1:"N";s:4:"慈";s:1:"N";s:4:"慌";s:1:"N";s:4:"慎";s:1:"N";s:4:"慌";s:1:"N";s:4:"慺";s:1:"N";s:4:"憎";s:1:"N";s:4:"憲";s:1:"N";s:4:"憤";s:1:"N";s:4:"憯";s:1:"N";s:4:"懞";s:1:"N";s:4:"懲";s:1:"N";s:4:"懶";s:1:"N";s:4:"成";s:1:"N";s:4:"戛";s:1:"N";s:4:"扝";s:1:"N";s:4:"抱";s:1:"N";s:4:"拔";s:1:"N";s:4:"捐";s:1:"N";s:4:"𢬌";s:1:"N";s:4:"挽";s:1:"N";s:4:"拼";s:1:"N";s:4:"捨";s:1:"N";s:4:"掃";s:1:"N";s:4:"揤";s:1:"N";s:4:"𢯱";s:1:"N";s:4:"搢";s:1:"N";s:4:"揅";s:1:"N";s:4:"掩";s:1:"N";s:4:"㨮";s:1:"N";s:4:"摩";s:1:"N";s:4:"摾";s:1:"N";s:4:"撝";s:1:"N";s:4:"摷";s:1:"N";s:4:"㩬";s:1:"N";s:4:"敏";s:1:"N";s:4:"敬";s:1:"N";s:4:"𣀊";s:1:"N";s:4:"旣";s:1:"N";s:4:"書";s:1:"N";s:4:"晉";s:1:"N";s:4:"㬙";s:1:"N";s:4:"暑";s:1:"N";s:4:"㬈";s:1:"N";s:4:"㫤";s:1:"N";s:4:"冒";s:1:"N";s:4:"冕";s:1:"N";s:4:"最";s:1:"N";s:4:"暜";s:1:"N";s:4:"肭";s:1:"N";s:4:"䏙";s:1:"N";s:4:"朗";s:1:"N";s:4:"望";s:1:"N";s:4:"朡";s:1:"N";s:4:"杞";s:1:"N";s:4:"杓";s:1:"N";s:4:"𣏃";s:1:"N";s:4:"㭉";s:1:"N";s:4:"柺";s:1:"N";s:4:"枅";s:1:"N";s:4:"桒";s:1:"N";s:4:"梅";s:1:"N";s:4:"𣑭";s:1:"N";s:4:"梎";s:1:"N";s:4:"栟";s:1:"N";s:4:"椔";s:1:"N";s:4:"㮝";s:1:"N";s:4:"楂";s:1:"N";s:4:"榣";s:1:"N";s:4:"槪";s:1:"N";s:4:"檨";s:1:"N";s:4:"𣚣";s:1:"N";s:4:"櫛";s:1:"N";s:4:"㰘";s:1:"N";s:4:"次";s:1:"N";s:4:"𣢧";s:1:"N";s:4:"歔";s:1:"N";s:4:"㱎";s:1:"N";s:4:"歲";s:1:"N";s:4:"殟";s:1:"N";s:4:"殺";s:1:"N";s:4:"殻";s:1:"N";s:4:"𣪍";s:1:"N";s:4:"𡴋";s:1:"N";s:4:"𣫺";s:1:"N";s:4:"汎";s:1:"N";s:4:"𣲼";s:1:"N";s:4:"沿";s:1:"N";s:4:"泍";s:1:"N";s:4:"汧";s:1:"N";s:4:"洖";s:1:"N";s:4:"派";s:1:"N";s:4:"海";s:1:"N";s:4:"流";s:1:"N";s:4:"浩";s:1:"N";s:4:"浸";s:1:"N";s:4:"涅";s:1:"N";s:4:"𣴞";s:1:"N";s:4:"洴";s:1:"N";s:4:"港";s:1:"N";s:4:"湮";s:1:"N";s:4:"㴳";s:1:"N";s:4:"滋";s:1:"N";s:4:"滇";s:1:"N";s:4:"𣻑";s:1:"N";s:4:"淹";s:1:"N";s:4:"潮";s:1:"N";s:4:"𣽞";s:1:"N";s:4:"𣾎";s:1:"N";s:4:"濆";s:1:"N";s:4:"瀹";s:1:"N";s:4:"瀞";s:1:"N";s:4:"瀛";s:1:"N";s:4:"㶖";s:1:"N";s:4:"灊";s:1:"N";s:4:"災";s:1:"N";s:4:"灷";s:1:"N";s:4:"炭";s:1:"N";s:4:"𠔥";s:1:"N";s:4:"煅";s:1:"N";s:4:"𤉣";s:1:"N";s:4:"熜";s:1:"N";s:4:"𤎫";s:1:"N";s:4:"爨";s:1:"N";s:4:"爵";s:1:"N";s:4:"牐";s:1:"N";s:4:"𤘈";s:1:"N";s:4:"犀";s:1:"N";s:4:"犕";s:1:"N";s:4:"𤜵";s:1:"N";s:4:"𤠔";s:1:"N";s:4:"獺";s:1:"N";s:4:"王";s:1:"N";s:4:"㺬";s:1:"N";s:4:"玥";s:1:"N";s:4:"㺸";s:1:"N";s:4:"㺸";s:1:"N";s:4:"瑇";s:1:"N";s:4:"瑜";s:1:"N";s:4:"瑱";s:1:"N";s:4:"璅";s:1:"N";s:4:"瓊";s:1:"N";s:4:"㼛";s:1:"N";s:4:"甤";s:1:"N";s:4:"𤰶";s:1:"N";s:4:"甾";s:1:"N";s:4:"𤲒";s:1:"N";s:4:"異";s:1:"N";s:4:"𢆟";s:1:"N";s:4:"瘐";s:1:"N";s:4:"𤾡";s:1:"N";s:4:"𤾸";s:1:"N";s:4:"𥁄";s:1:"N";s:4:"㿼";s:1:"N";s:4:"䀈";s:1:"N";s:4:"直";s:1:"N";s:4:"𥃳";s:1:"N";s:4:"𥃲";s:1:"N";s:4:"𥄙";s:1:"N";s:4:"𥄳";s:1:"N";s:4:"眞";s:1:"N";s:4:"真";s:1:"N";s:4:"真";s:1:"N";s:4:"睊";s:1:"N";s:4:"䀹";s:1:"N";s:4:"瞋";s:1:"N";s:4:"䁆";s:1:"N";s:4:"䂖";s:1:"N";s:4:"𥐝";s:1:"N";s:4:"硎";s:1:"N";s:4:"碌";s:1:"N";s:4:"磌";s:1:"N";s:4:"䃣";s:1:"N";s:4:"𥘦";s:1:"N";s:4:"祖";s:1:"N";s:4:"𥚚";s:1:"N";s:4:"𥛅";s:1:"N";s:4:"福";s:1:"N";s:4:"秫";s:1:"N";s:4:"䄯";s:1:"N";s:4:"穀";s:1:"N";s:4:"穊";s:1:"N";s:4:"穏";s:1:"N";s:4:"𥥼";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"𥪧";s:1:"N";s:4:"竮";s:1:"N";s:4:"䈂";s:1:"N";s:4:"𥮫";s:1:"N";s:4:"篆";s:1:"N";s:4:"築";s:1:"N";s:4:"䈧";s:1:"N";s:4:"𥲀";s:1:"N";s:4:"糒";s:1:"N";s:4:"䊠";s:1:"N";s:4:"糨";s:1:"N";s:4:"糣";s:1:"N";s:4:"紀";s:1:"N";s:4:"𥾆";s:1:"N";s:4:"絣";s:1:"N";s:4:"䌁";s:1:"N";s:4:"緇";s:1:"N";s:4:"縂";s:1:"N";s:4:"繅";s:1:"N";s:4:"䌴";s:1:"N";s:4:"𦈨";s:1:"N";s:4:"𦉇";s:1:"N";s:4:"䍙";s:1:"N";s:4:"𦋙";s:1:"N";s:4:"罺";s:1:"N";s:4:"𦌾";s:1:"N";s:4:"羕";s:1:"N";s:4:"翺";s:1:"N";s:4:"者";s:1:"N";s:4:"𦓚";s:1:"N";s:4:"𦔣";s:1:"N";s:4:"聠";s:1:"N";s:4:"𦖨";s:1:"N";s:4:"聰";s:1:"N";s:4:"𣍟";s:1:"N";s:4:"䏕";s:1:"N";s:4:"育";s:1:"N";s:4:"脃";s:1:"N";s:4:"䐋";s:1:"N";s:4:"脾";s:1:"N";s:4:"媵";s:1:"N";s:4:"𦞧";s:1:"N";s:4:"𦞵";s:1:"N";s:4:"𣎓";s:1:"N";s:4:"𣎜";s:1:"N";s:4:"舁";s:1:"N";s:4:"舄";s:1:"N";s:4:"辞";s:1:"N";s:4:"䑫";s:1:"N";s:4:"芑";s:1:"N";s:4:"芋";s:1:"N";s:4:"芝";s:1:"N";s:4:"劳";s:1:"N";s:4:"花";s:1:"N";s:4:"芳";s:1:"N";s:4:"芽";s:1:"N";s:4:"苦";s:1:"N";s:4:"𦬼";s:1:"N";s:4:"若";s:1:"N";s:4:"茝";s:1:"N";s:4:"荣";s:1:"N";s:4:"莭";s:1:"N";s:4:"茣";s:1:"N";s:4:"莽";s:1:"N";s:4:"菧";s:1:"N";s:4:"著";s:1:"N";s:4:"荓";s:1:"N";s:4:"菊";s:1:"N";s:4:"菌";s:1:"N";s:4:"菜";s:1:"N";s:4:"𦰶";s:1:"N";s:4:"𦵫";s:1:"N";s:4:"𦳕";s:1:"N";s:4:"䔫";s:1:"N";s:4:"蓱";s:1:"N";s:4:"蓳";s:1:"N";s:4:"蔖";s:1:"N";s:4:"𧏊";s:1:"N";s:4:"蕤";s:1:"N";s:4:"𦼬";s:1:"N";s:4:"䕝";s:1:"N";s:4:"䕡";s:1:"N";s:4:"𦾱";s:1:"N";s:4:"𧃒";s:1:"N";s:4:"䕫";s:1:"N";s:4:"虐";s:1:"N";s:4:"虜";s:1:"N";s:4:"虧";s:1:"N";s:4:"虩";s:1:"N";s:4:"蚩";s:1:"N";s:4:"蚈";s:1:"N";s:4:"蜎";s:1:"N";s:4:"蛢";s:1:"N";s:4:"蝹";s:1:"N";s:4:"蜨";s:1:"N";s:4:"蝫";s:1:"N";s:4:"螆";s:1:"N";s:4:"䗗";s:1:"N";s:4:"蟡";s:1:"N";s:4:"蠁";s:1:"N";s:4:"䗹";s:1:"N";s:4:"衠";s:1:"N";s:4:"衣";s:1:"N";s:4:"𧙧";s:1:"N";s:4:"裗";s:1:"N";s:4:"裞";s:1:"N";s:4:"䘵";s:1:"N";s:4:"裺";s:1:"N";s:4:"㒻";s:1:"N";s:4:"𧢮";s:1:"N";s:4:"𧥦";s:1:"N";s:4:"䚾";s:1:"N";s:4:"䛇";s:1:"N";s:4:"誠";s:1:"N";s:4:"諭";s:1:"N";s:4:"變";s:1:"N";s:4:"豕";s:1:"N";s:4:"𧲨";s:1:"N";s:4:"貫";s:1:"N";s:4:"賁";s:1:"N";s:4:"贛";s:1:"N";s:4:"起";s:1:"N";s:4:"𧼯";s:1:"N";s:4:"𠠄";s:1:"N";s:4:"跋";s:1:"N";s:4:"趼";s:1:"N";s:4:"跰";s:1:"N";s:4:"𠣞";s:1:"N";s:4:"軔";s:1:"N";s:4:"輸";s:1:"N";s:4:"𨗒";s:1:"N";s:4:"𨗭";s:1:"N";s:4:"邔";s:1:"N";s:4:"郱";s:1:"N";s:4:"鄑";s:1:"N";s:4:"𨜮";s:1:"N";s:4:"鄛";s:1:"N";s:4:"鈸";s:1:"N";s:4:"鋗";s:1:"N";s:4:"鋘";s:1:"N";s:4:"鉼";s:1:"N";s:4:"鏹";s:1:"N";s:4:"鐕";s:1:"N";s:4:"𨯺";s:1:"N";s:4:"開";s:1:"N";s:4:"䦕";s:1:"N";s:4:"閷";s:1:"N";s:4:"𨵷";s:1:"N";s:4:"䧦";s:1:"N";s:4:"雃";s:1:"N";s:4:"嶲";s:1:"N";s:4:"霣";s:1:"N";s:4:"𩅅";s:1:"N";s:4:"𩈚";s:1:"N";s:4:"䩮";s:1:"N";s:4:"䩶";s:1:"N";s:4:"韠";s:1:"N";s:4:"𩐊";s:1:"N";s:4:"䪲";s:1:"N";s:4:"𩒖";s:1:"N";s:4:"頋";s:1:"N";s:4:"頋";s:1:"N";s:4:"頩";s:1:"N";s:4:"𩖶";s:1:"N";s:4:"飢";s:1:"N";s:4:"䬳";s:1:"N";s:4:"餩";s:1:"N";s:4:"馧";s:1:"N";s:4:"駂";s:1:"N";s:4:"駾";s:1:"N";s:4:"䯎";s:1:"N";s:4:"𩬰";s:1:"N";s:4:"鬒";s:1:"N";s:4:"鱀";s:1:"N";s:4:"鳽";s:1:"N";s:4:"䳎";s:1:"N";s:4:"䳭";s:1:"N";s:4:"鵧";s:1:"N";s:4:"𪃎";s:1:"N";s:4:"䳸";s:1:"N";s:4:"𪄅";s:1:"N";s:4:"𪈎";s:1:"N";s:4:"𪊑";s:1:"N";s:4:"麻";s:1:"N";s:4:"䵖";s:1:"N";s:4:"黹";s:1:"N";s:4:"黾";s:1:"N";s:4:"鼅";s:1:"N";s:4:"鼏";s:1:"N";s:4:"鼖";s:1:"N";s:4:"鼻";s:1:"N";s:4:"𪘀";s:1:"N";s:2:"̀";s:1:"M";s:2:"́";s:1:"M";s:2:"̂";s:1:"M";s:2:"̃";s:1:"M";s:2:"̄";s:1:"M";s:2:"̆";s:1:"M";s:2:"̇";s:1:"M";s:2:"̈";s:1:"M";s:2:"̉";s:1:"M";s:2:"̊";s:1:"M";s:2:"̋";s:1:"M";s:2:"̌";s:1:"M";s:2:"̏";s:1:"M";s:2:"̑";s:1:"M";s:2:"̓";s:1:"M";s:2:"̔";s:1:"M";s:2:"̛";s:1:"M";s:2:"̣";s:1:"M";s:2:"̤";s:1:"M";s:2:"̥";s:1:"M";s:2:"̦";s:1:"M";s:2:"̧";s:1:"M";s:2:"̨";s:1:"M";s:2:"̭";s:1:"M";s:2:"̮";s:1:"M";s:2:"̰";s:1:"M";s:2:"̱";s:1:"M";s:2:"̸";s:1:"M";s:2:"͂";s:1:"M";s:2:"ͅ";s:1:"M";s:2:"ٓ";s:1:"M";s:2:"ٔ";s:1:"M";s:2:"ٕ";s:1:"M";s:3:"़";s:1:"M";s:3:"া";s:1:"M";s:3:"ৗ";s:1:"M";s:3:"ା";s:1:"M";s:3:"ୖ";s:1:"M";s:3:"ୗ";s:1:"M";s:3:"ா";s:1:"M";s:3:"ௗ";s:1:"M";s:3:"ౖ";s:1:"M";s:3:"ೂ";s:1:"M";s:3:"ೕ";s:1:"M";s:3:"ೖ";s:1:"M";s:3:"ാ";s:1:"M";s:3:"ൗ";s:1:"M";s:3:"්";s:1:"M";s:3:"ා";s:1:"M";s:3:"ෟ";s:1:"M";s:3:"ီ";s:1:"M";s:3:"ᅡ";s:1:"M";s:3:"ᅢ";s:1:"M";s:3:"ᅣ";s:1:"M";s:3:"ᅤ";s:1:"M";s:3:"ᅥ";s:1:"M";s:3:"ᅦ";s:1:"M";s:3:"ᅧ";s:1:"M";s:3:"ᅨ";s:1:"M";s:3:"ᅩ";s:1:"M";s:3:"ᅪ";s:1:"M";s:3:"ᅫ";s:1:"M";s:3:"ᅬ";s:1:"M";s:3:"ᅭ";s:1:"M";s:3:"ᅮ";s:1:"M";s:3:"ᅯ";s:1:"M";s:3:"ᅰ";s:1:"M";s:3:"ᅱ";s:1:"M";s:3:"ᅲ";s:1:"M";s:3:"ᅳ";s:1:"M";s:3:"ᅴ";s:1:"M";s:3:"ᅵ";s:1:"M";s:3:"ᆨ";s:1:"M";s:3:"ᆩ";s:1:"M";s:3:"ᆪ";s:1:"M";s:3:"ᆫ";s:1:"M";s:3:"ᆬ";s:1:"M";s:3:"ᆭ";s:1:"M";s:3:"ᆮ";s:1:"M";s:3:"ᆯ";s:1:"M";s:3:"ᆰ";s:1:"M";s:3:"ᆱ";s:1:"M";s:3:"ᆲ";s:1:"M";s:3:"ᆳ";s:1:"M";s:3:"ᆴ";s:1:"M";s:3:"ᆵ";s:1:"M";s:3:"ᆶ";s:1:"M";s:3:"ᆷ";s:1:"M";s:3:"ᆸ";s:1:"M";s:3:"ᆹ";s:1:"M";s:3:"ᆺ";s:1:"M";s:3:"ᆻ";s:1:"M";s:3:"ᆼ";s:1:"M";s:3:"ᆽ";s:1:"M";s:3:"ᆾ";s:1:"M";s:3:"ᆿ";s:1:"M";s:3:"ᇀ";s:1:"M";s:3:"ᇁ";s:1:"M";s:3:"ᇂ";s:1:"M";s:3:"ᬵ";s:1:"M";s:3:"゙";s:1:"M";s:3:"゚";s:1:"M";s:4:"𑂺";s:1:"M";}' );
+
diff --git a/vendor/wikimedia/utfnormal/src/UtfNormalDataK.inc b/vendor/wikimedia/utfnormal/src/UtfNormalDataK.inc
new file mode 100644
index 00000000..8f979e3b
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/src/UtfNormalDataK.inc
@@ -0,0 +1,11 @@
+<?php
+/**
+ * This file was automatically generated -- do not edit!
+ * Run UtfNormalGenerate.php to create this file again (make clean && make)
+ *
+ * @file
+ */
+// @codingStandardsIgnoreFile
+
+UtfNormal\Validator::$utfCompatibilityDecomp = unserialize( 'a:5559:{s:2:" ";s:1:" ";s:2:"¨";s:3:" ̈";s:2:"ª";s:1:"a";s:2:"¯";s:3:" ̄";s:2:"²";s:1:"2";s:2:"³";s:1:"3";s:2:"´";s:3:" ́";s:2:"µ";s:2:"μ";s:2:"¸";s:3:" ̧";s:2:"¹";s:1:"1";s:2:"º";s:1:"o";s:2:"¼";s:5:"1⁄4";s:2:"½";s:5:"1⁄2";s:2:"¾";s:5:"3⁄4";s:2:"À";s:3:"À";s:2:"Á";s:3:"Á";s:2:"Â";s:3:"Â";s:2:"Ã";s:3:"Ã";s:2:"Ä";s:3:"Ä";s:2:"Å";s:3:"Å";s:2:"Ç";s:3:"Ç";s:2:"È";s:3:"È";s:2:"É";s:3:"É";s:2:"Ê";s:3:"Ê";s:2:"Ë";s:3:"Ë";s:2:"Ì";s:3:"Ì";s:2:"Í";s:3:"Í";s:2:"Î";s:3:"Î";s:2:"Ï";s:3:"Ï";s:2:"Ñ";s:3:"Ñ";s:2:"Ò";s:3:"Ò";s:2:"Ó";s:3:"Ó";s:2:"Ô";s:3:"Ô";s:2:"Õ";s:3:"Õ";s:2:"Ö";s:3:"Ö";s:2:"Ù";s:3:"Ù";s:2:"Ú";s:3:"Ú";s:2:"Û";s:3:"Û";s:2:"Ü";s:3:"Ü";s:2:"Ý";s:3:"Ý";s:2:"à";s:3:"à";s:2:"á";s:3:"á";s:2:"â";s:3:"â";s:2:"ã";s:3:"ã";s:2:"ä";s:3:"ä";s:2:"å";s:3:"å";s:2:"ç";s:3:"ç";s:2:"è";s:3:"è";s:2:"é";s:3:"é";s:2:"ê";s:3:"ê";s:2:"ë";s:3:"ë";s:2:"ì";s:3:"ì";s:2:"í";s:3:"í";s:2:"î";s:3:"î";s:2:"ï";s:3:"ï";s:2:"ñ";s:3:"ñ";s:2:"ò";s:3:"ò";s:2:"ó";s:3:"ó";s:2:"ô";s:3:"ô";s:2:"õ";s:3:"õ";s:2:"ö";s:3:"ö";s:2:"ù";s:3:"ù";s:2:"ú";s:3:"ú";s:2:"û";s:3:"û";s:2:"ü";s:3:"ü";s:2:"ý";s:3:"ý";s:2:"ÿ";s:3:"ÿ";s:2:"Ā";s:3:"Ā";s:2:"ā";s:3:"ā";s:2:"Ă";s:3:"Ă";s:2:"ă";s:3:"ă";s:2:"Ą";s:3:"Ą";s:2:"ą";s:3:"ą";s:2:"Ć";s:3:"Ć";s:2:"ć";s:3:"ć";s:2:"Ĉ";s:3:"Ĉ";s:2:"ĉ";s:3:"ĉ";s:2:"Ċ";s:3:"Ċ";s:2:"ċ";s:3:"ċ";s:2:"Č";s:3:"Č";s:2:"č";s:3:"č";s:2:"Ď";s:3:"Ď";s:2:"ď";s:3:"ď";s:2:"Ē";s:3:"Ē";s:2:"ē";s:3:"ē";s:2:"Ĕ";s:3:"Ĕ";s:2:"ĕ";s:3:"ĕ";s:2:"Ė";s:3:"Ė";s:2:"ė";s:3:"ė";s:2:"Ę";s:3:"Ę";s:2:"ę";s:3:"ę";s:2:"Ě";s:3:"Ě";s:2:"ě";s:3:"ě";s:2:"Ĝ";s:3:"Ĝ";s:2:"ĝ";s:3:"ĝ";s:2:"Ğ";s:3:"Ğ";s:2:"ğ";s:3:"ğ";s:2:"Ġ";s:3:"Ġ";s:2:"ġ";s:3:"ġ";s:2:"Ģ";s:3:"Ģ";s:2:"ģ";s:3:"ģ";s:2:"Ĥ";s:3:"Ĥ";s:2:"ĥ";s:3:"ĥ";s:2:"Ĩ";s:3:"Ĩ";s:2:"ĩ";s:3:"ĩ";s:2:"Ī";s:3:"Ī";s:2:"ī";s:3:"ī";s:2:"Ĭ";s:3:"Ĭ";s:2:"ĭ";s:3:"ĭ";s:2:"Į";s:3:"Į";s:2:"į";s:3:"į";s:2:"İ";s:3:"İ";s:2:"IJ";s:2:"IJ";s:2:"ij";s:2:"ij";s:2:"Ĵ";s:3:"Ĵ";s:2:"ĵ";s:3:"ĵ";s:2:"Ķ";s:3:"Ķ";s:2:"ķ";s:3:"ķ";s:2:"Ĺ";s:3:"Ĺ";s:2:"ĺ";s:3:"ĺ";s:2:"Ļ";s:3:"Ļ";s:2:"ļ";s:3:"ļ";s:2:"Ľ";s:3:"Ľ";s:2:"ľ";s:3:"ľ";s:2:"Ŀ";s:3:"L·";s:2:"ŀ";s:3:"l·";s:2:"Ń";s:3:"Ń";s:2:"ń";s:3:"ń";s:2:"Ņ";s:3:"Ņ";s:2:"ņ";s:3:"ņ";s:2:"Ň";s:3:"Ň";s:2:"ň";s:3:"ň";s:2:"ʼn";s:3:"ʼn";s:2:"Ō";s:3:"Ō";s:2:"ō";s:3:"ō";s:2:"Ŏ";s:3:"Ŏ";s:2:"ŏ";s:3:"ŏ";s:2:"Ő";s:3:"Ő";s:2:"ő";s:3:"ő";s:2:"Ŕ";s:3:"Ŕ";s:2:"ŕ";s:3:"ŕ";s:2:"Ŗ";s:3:"Ŗ";s:2:"ŗ";s:3:"ŗ";s:2:"Ř";s:3:"Ř";s:2:"ř";s:3:"ř";s:2:"Ś";s:3:"Ś";s:2:"ś";s:3:"ś";s:2:"Ŝ";s:3:"Ŝ";s:2:"ŝ";s:3:"ŝ";s:2:"Ş";s:3:"Ş";s:2:"ş";s:3:"ş";s:2:"Š";s:3:"Š";s:2:"š";s:3:"š";s:2:"Ţ";s:3:"Ţ";s:2:"ţ";s:3:"ţ";s:2:"Ť";s:3:"Ť";s:2:"ť";s:3:"ť";s:2:"Ũ";s:3:"Ũ";s:2:"ũ";s:3:"ũ";s:2:"Ū";s:3:"Ū";s:2:"ū";s:3:"ū";s:2:"Ŭ";s:3:"Ŭ";s:2:"ŭ";s:3:"ŭ";s:2:"Ů";s:3:"Ů";s:2:"ů";s:3:"ů";s:2:"Ű";s:3:"Ű";s:2:"ű";s:3:"ű";s:2:"Ų";s:3:"Ų";s:2:"ų";s:3:"ų";s:2:"Ŵ";s:3:"Ŵ";s:2:"ŵ";s:3:"ŵ";s:2:"Ŷ";s:3:"Ŷ";s:2:"ŷ";s:3:"ŷ";s:2:"Ÿ";s:3:"Ÿ";s:2:"Ź";s:3:"Ź";s:2:"ź";s:3:"ź";s:2:"Ż";s:3:"Ż";s:2:"ż";s:3:"ż";s:2:"Ž";s:3:"Ž";s:2:"ž";s:3:"ž";s:2:"ſ";s:1:"s";s:2:"Ơ";s:3:"Ơ";s:2:"ơ";s:3:"ơ";s:2:"Ư";s:3:"Ư";s:2:"ư";s:3:"ư";s:2:"DŽ";s:4:"DŽ";s:2:"Dž";s:4:"Dž";s:2:"dž";s:4:"dž";s:2:"LJ";s:2:"LJ";s:2:"Lj";s:2:"Lj";s:2:"lj";s:2:"lj";s:2:"NJ";s:2:"NJ";s:2:"Nj";s:2:"Nj";s:2:"nj";s:2:"nj";s:2:"Ǎ";s:3:"Ǎ";s:2:"ǎ";s:3:"ǎ";s:2:"Ǐ";s:3:"Ǐ";s:2:"ǐ";s:3:"ǐ";s:2:"Ǒ";s:3:"Ǒ";s:2:"ǒ";s:3:"ǒ";s:2:"Ǔ";s:3:"Ǔ";s:2:"ǔ";s:3:"ǔ";s:2:"Ǖ";s:5:"Ǖ";s:2:"ǖ";s:5:"ǖ";s:2:"Ǘ";s:5:"Ǘ";s:2:"ǘ";s:5:"ǘ";s:2:"Ǚ";s:5:"Ǚ";s:2:"ǚ";s:5:"ǚ";s:2:"Ǜ";s:5:"Ǜ";s:2:"ǜ";s:5:"ǜ";s:2:"Ǟ";s:5:"Ǟ";s:2:"ǟ";s:5:"ǟ";s:2:"Ǡ";s:5:"Ǡ";s:2:"ǡ";s:5:"ǡ";s:2:"Ǣ";s:4:"Ǣ";s:2:"ǣ";s:4:"ǣ";s:2:"Ǧ";s:3:"Ǧ";s:2:"ǧ";s:3:"ǧ";s:2:"Ǩ";s:3:"Ǩ";s:2:"ǩ";s:3:"ǩ";s:2:"Ǫ";s:3:"Ǫ";s:2:"ǫ";s:3:"ǫ";s:2:"Ǭ";s:5:"Ǭ";s:2:"ǭ";s:5:"ǭ";s:2:"Ǯ";s:4:"Ǯ";s:2:"ǯ";s:4:"ǯ";s:2:"ǰ";s:3:"ǰ";s:2:"DZ";s:2:"DZ";s:2:"Dz";s:2:"Dz";s:2:"dz";s:2:"dz";s:2:"Ǵ";s:3:"Ǵ";s:2:"ǵ";s:3:"ǵ";s:2:"Ǹ";s:3:"Ǹ";s:2:"ǹ";s:3:"ǹ";s:2:"Ǻ";s:5:"Ǻ";s:2:"ǻ";s:5:"ǻ";s:2:"Ǽ";s:4:"Ǽ";s:2:"ǽ";s:4:"ǽ";s:2:"Ǿ";s:4:"Ǿ";s:2:"ǿ";s:4:"ǿ";s:2:"Ȁ";s:3:"Ȁ";s:2:"ȁ";s:3:"ȁ";s:2:"Ȃ";s:3:"Ȃ";s:2:"ȃ";s:3:"ȃ";s:2:"Ȅ";s:3:"Ȅ";s:2:"ȅ";s:3:"ȅ";s:2:"Ȇ";s:3:"Ȇ";s:2:"ȇ";s:3:"ȇ";s:2:"Ȉ";s:3:"Ȉ";s:2:"ȉ";s:3:"ȉ";s:2:"Ȋ";s:3:"Ȋ";s:2:"ȋ";s:3:"ȋ";s:2:"Ȍ";s:3:"Ȍ";s:2:"ȍ";s:3:"ȍ";s:2:"Ȏ";s:3:"Ȏ";s:2:"ȏ";s:3:"ȏ";s:2:"Ȑ";s:3:"Ȑ";s:2:"ȑ";s:3:"ȑ";s:2:"Ȓ";s:3:"Ȓ";s:2:"ȓ";s:3:"ȓ";s:2:"Ȕ";s:3:"Ȕ";s:2:"ȕ";s:3:"ȕ";s:2:"Ȗ";s:3:"Ȗ";s:2:"ȗ";s:3:"ȗ";s:2:"Ș";s:3:"Ș";s:2:"ș";s:3:"ș";s:2:"Ț";s:3:"Ț";s:2:"ț";s:3:"ț";s:2:"Ȟ";s:3:"Ȟ";s:2:"ȟ";s:3:"ȟ";s:2:"Ȧ";s:3:"Ȧ";s:2:"ȧ";s:3:"ȧ";s:2:"Ȩ";s:3:"Ȩ";s:2:"ȩ";s:3:"ȩ";s:2:"Ȫ";s:5:"Ȫ";s:2:"ȫ";s:5:"ȫ";s:2:"Ȭ";s:5:"Ȭ";s:2:"ȭ";s:5:"ȭ";s:2:"Ȯ";s:3:"Ȯ";s:2:"ȯ";s:3:"ȯ";s:2:"Ȱ";s:5:"Ȱ";s:2:"ȱ";s:5:"ȱ";s:2:"Ȳ";s:3:"Ȳ";s:2:"ȳ";s:3:"ȳ";s:2:"ʰ";s:1:"h";s:2:"ʱ";s:2:"ɦ";s:2:"ʲ";s:1:"j";s:2:"ʳ";s:1:"r";s:2:"ʴ";s:2:"ɹ";s:2:"ʵ";s:2:"ɻ";s:2:"ʶ";s:2:"ʁ";s:2:"ʷ";s:1:"w";s:2:"ʸ";s:1:"y";s:2:"˘";s:3:" ̆";s:2:"˙";s:3:" ̇";s:2:"˚";s:3:" ̊";s:2:"˛";s:3:" ̨";s:2:"˜";s:3:" ̃";s:2:"˝";s:3:" ̋";s:2:"ˠ";s:2:"ɣ";s:2:"ˡ";s:1:"l";s:2:"ˢ";s:1:"s";s:2:"ˣ";s:1:"x";s:2:"ˤ";s:2:"ʕ";s:2:"̀";s:2:"̀";s:2:"́";s:2:"́";s:2:"̓";s:2:"̓";s:2:"̈́";s:4:"̈́";s:2:"ʹ";s:2:"ʹ";s:2:"ͺ";s:3:" ͅ";s:2:";";s:1:";";s:2:"΄";s:3:" ́";s:2:"΅";s:5:" ̈́";s:2:"Ά";s:4:"Ά";s:2:"·";s:2:"·";s:2:"Έ";s:4:"Έ";s:2:"Ή";s:4:"Ή";s:2:"Ί";s:4:"Ί";s:2:"Ό";s:4:"Ό";s:2:"Ύ";s:4:"Ύ";s:2:"Ώ";s:4:"Ώ";s:2:"ΐ";s:6:"ΐ";s:2:"Ϊ";s:4:"Ϊ";s:2:"Ϋ";s:4:"Ϋ";s:2:"ά";s:4:"ά";s:2:"έ";s:4:"έ";s:2:"ή";s:4:"ή";s:2:"ί";s:4:"ί";s:2:"ΰ";s:6:"ΰ";s:2:"ϊ";s:4:"ϊ";s:2:"ϋ";s:4:"ϋ";s:2:"ό";s:4:"ό";s:2:"ύ";s:4:"ύ";s:2:"ώ";s:4:"ώ";s:2:"ϐ";s:2:"β";s:2:"ϑ";s:2:"θ";s:2:"ϒ";s:2:"Υ";s:2:"ϓ";s:4:"Ύ";s:2:"ϔ";s:4:"Ϋ";s:2:"ϕ";s:2:"φ";s:2:"ϖ";s:2:"π";s:2:"ϰ";s:2:"κ";s:2:"ϱ";s:2:"ρ";s:2:"ϲ";s:2:"ς";s:2:"ϴ";s:2:"Θ";s:2:"ϵ";s:2:"ε";s:2:"Ϲ";s:2:"Σ";s:2:"Ѐ";s:4:"Ѐ";s:2:"Ё";s:4:"Ё";s:2:"Ѓ";s:4:"Ѓ";s:2:"Ї";s:4:"Ї";s:2:"Ќ";s:4:"Ќ";s:2:"Ѝ";s:4:"Ѝ";s:2:"Ў";s:4:"Ў";s:2:"Й";s:4:"Й";s:2:"й";s:4:"й";s:2:"ѐ";s:4:"ѐ";s:2:"ё";s:4:"ё";s:2:"ѓ";s:4:"ѓ";s:2:"ї";s:4:"ї";s:2:"ќ";s:4:"ќ";s:2:"ѝ";s:4:"ѝ";s:2:"ў";s:4:"ў";s:2:"Ѷ";s:4:"Ѷ";s:2:"ѷ";s:4:"ѷ";s:2:"Ӂ";s:4:"Ӂ";s:2:"ӂ";s:4:"ӂ";s:2:"Ӑ";s:4:"Ӑ";s:2:"ӑ";s:4:"ӑ";s:2:"Ӓ";s:4:"Ӓ";s:2:"ӓ";s:4:"ӓ";s:2:"Ӗ";s:4:"Ӗ";s:2:"ӗ";s:4:"ӗ";s:2:"Ӛ";s:4:"Ӛ";s:2:"ӛ";s:4:"ӛ";s:2:"Ӝ";s:4:"Ӝ";s:2:"ӝ";s:4:"ӝ";s:2:"Ӟ";s:4:"Ӟ";s:2:"ӟ";s:4:"ӟ";s:2:"Ӣ";s:4:"Ӣ";s:2:"ӣ";s:4:"ӣ";s:2:"Ӥ";s:4:"Ӥ";s:2:"ӥ";s:4:"ӥ";s:2:"Ӧ";s:4:"Ӧ";s:2:"ӧ";s:4:"ӧ";s:2:"Ӫ";s:4:"Ӫ";s:2:"ӫ";s:4:"ӫ";s:2:"Ӭ";s:4:"Ӭ";s:2:"ӭ";s:4:"ӭ";s:2:"Ӯ";s:4:"Ӯ";s:2:"ӯ";s:4:"ӯ";s:2:"Ӱ";s:4:"Ӱ";s:2:"ӱ";s:4:"ӱ";s:2:"Ӳ";s:4:"Ӳ";s:2:"ӳ";s:4:"ӳ";s:2:"Ӵ";s:4:"Ӵ";s:2:"ӵ";s:4:"ӵ";s:2:"Ӹ";s:4:"Ӹ";s:2:"ӹ";s:4:"ӹ";s:2:"և";s:4:"եւ";s:2:"آ";s:4:"آ";s:2:"أ";s:4:"أ";s:2:"ؤ";s:4:"ؤ";s:2:"إ";s:4:"إ";s:2:"ئ";s:4:"ئ";s:2:"ٵ";s:4:"اٴ";s:2:"ٶ";s:4:"وٴ";s:2:"ٷ";s:4:"ۇٴ";s:2:"ٸ";s:4:"يٴ";s:2:"ۀ";s:4:"ۀ";s:2:"ۂ";s:4:"ۂ";s:2:"ۓ";s:4:"ۓ";s:3:"ऩ";s:6:"ऩ";s:3:"ऱ";s:6:"ऱ";s:3:"ऴ";s:6:"ऴ";s:3:"क़";s:6:"क़";s:3:"ख़";s:6:"ख़";s:3:"ग़";s:6:"ग़";s:3:"ज़";s:6:"ज़";s:3:"ड़";s:6:"ड़";s:3:"ढ़";s:6:"ढ़";s:3:"फ़";s:6:"फ़";s:3:"य़";s:6:"य़";s:3:"ো";s:6:"ো";s:3:"ৌ";s:6:"ৌ";s:3:"ড়";s:6:"ড়";s:3:"ঢ়";s:6:"ঢ়";s:3:"য়";s:6:"য়";s:3:"ਲ਼";s:6:"ਲ਼";s:3:"ਸ਼";s:6:"ਸ਼";s:3:"ਖ਼";s:6:"ਖ਼";s:3:"ਗ਼";s:6:"ਗ਼";s:3:"ਜ਼";s:6:"ਜ਼";s:3:"ਫ਼";s:6:"ਫ਼";s:3:"ୈ";s:6:"ୈ";s:3:"ୋ";s:6:"ୋ";s:3:"ୌ";s:6:"ୌ";s:3:"ଡ଼";s:6:"ଡ଼";s:3:"ଢ଼";s:6:"ଢ଼";s:3:"ஔ";s:6:"ஔ";s:3:"ொ";s:6:"ொ";s:3:"ோ";s:6:"ோ";s:3:"ௌ";s:6:"ௌ";s:3:"ై";s:6:"ై";s:3:"ೀ";s:6:"ೀ";s:3:"ೇ";s:6:"ೇ";s:3:"ೈ";s:6:"ೈ";s:3:"ೊ";s:6:"ೊ";s:3:"ೋ";s:9:"ೋ";s:3:"ൊ";s:6:"ൊ";s:3:"ോ";s:6:"ോ";s:3:"ൌ";s:6:"ൌ";s:3:"ේ";s:6:"ේ";s:3:"ො";s:6:"ො";s:3:"ෝ";s:9:"ෝ";s:3:"ෞ";s:6:"ෞ";s:3:"ำ";s:6:"ํา";s:3:"ຳ";s:6:"ໍາ";s:3:"ໜ";s:6:"ຫນ";s:3:"ໝ";s:6:"ຫມ";s:3:"༌";s:3:"་";s:3:"གྷ";s:6:"གྷ";s:3:"ཌྷ";s:6:"ཌྷ";s:3:"དྷ";s:6:"དྷ";s:3:"བྷ";s:6:"བྷ";s:3:"ཛྷ";s:6:"ཛྷ";s:3:"ཀྵ";s:6:"ཀྵ";s:3:"ཱི";s:6:"ཱི";s:3:"ཱུ";s:6:"ཱུ";s:3:"ྲྀ";s:6:"ྲྀ";s:3:"ཷ";s:9:"ྲཱྀ";s:3:"ླྀ";s:6:"ླྀ";s:3:"ཹ";s:9:"ླཱྀ";s:3:"ཱྀ";s:6:"ཱྀ";s:3:"ྒྷ";s:6:"ྒྷ";s:3:"ྜྷ";s:6:"ྜྷ";s:3:"ྡྷ";s:6:"ྡྷ";s:3:"ྦྷ";s:6:"ྦྷ";s:3:"ྫྷ";s:6:"ྫྷ";s:3:"ྐྵ";s:6:"ྐྵ";s:3:"ဦ";s:6:"ဦ";s:3:"ჼ";s:3:"ნ";s:3:"ᬆ";s:6:"ᬆ";s:3:"ᬈ";s:6:"ᬈ";s:3:"ᬊ";s:6:"ᬊ";s:3:"ᬌ";s:6:"ᬌ";s:3:"ᬎ";s:6:"ᬎ";s:3:"ᬒ";s:6:"ᬒ";s:3:"ᬻ";s:6:"ᬻ";s:3:"ᬽ";s:6:"ᬽ";s:3:"ᭀ";s:6:"ᭀ";s:3:"ᭁ";s:6:"ᭁ";s:3:"ᭃ";s:6:"ᭃ";s:3:"ᴬ";s:1:"A";s:3:"ᴭ";s:2:"Æ";s:3:"ᴮ";s:1:"B";s:3:"ᴰ";s:1:"D";s:3:"ᴱ";s:1:"E";s:3:"ᴲ";s:2:"Ǝ";s:3:"ᴳ";s:1:"G";s:3:"ᴴ";s:1:"H";s:3:"ᴵ";s:1:"I";s:3:"ᴶ";s:1:"J";s:3:"ᴷ";s:1:"K";s:3:"ᴸ";s:1:"L";s:3:"ᴹ";s:1:"M";s:3:"ᴺ";s:1:"N";s:3:"ᴼ";s:1:"O";s:3:"ᴽ";s:2:"Ȣ";s:3:"ᴾ";s:1:"P";s:3:"ᴿ";s:1:"R";s:3:"ᵀ";s:1:"T";s:3:"ᵁ";s:1:"U";s:3:"ᵂ";s:1:"W";s:3:"ᵃ";s:1:"a";s:3:"ᵄ";s:2:"ɐ";s:3:"ᵅ";s:2:"ɑ";s:3:"ᵆ";s:3:"ᴂ";s:3:"ᵇ";s:1:"b";s:3:"ᵈ";s:1:"d";s:3:"ᵉ";s:1:"e";s:3:"ᵊ";s:2:"ə";s:3:"ᵋ";s:2:"ɛ";s:3:"ᵌ";s:2:"ɜ";s:3:"ᵍ";s:1:"g";s:3:"ᵏ";s:1:"k";s:3:"ᵐ";s:1:"m";s:3:"ᵑ";s:2:"ŋ";s:3:"ᵒ";s:1:"o";s:3:"ᵓ";s:2:"ɔ";s:3:"ᵔ";s:3:"ᴖ";s:3:"ᵕ";s:3:"ᴗ";s:3:"ᵖ";s:1:"p";s:3:"ᵗ";s:1:"t";s:3:"ᵘ";s:1:"u";s:3:"ᵙ";s:3:"ᴝ";s:3:"ᵚ";s:2:"ɯ";s:3:"ᵛ";s:1:"v";s:3:"ᵜ";s:3:"ᴥ";s:3:"ᵝ";s:2:"β";s:3:"ᵞ";s:2:"γ";s:3:"ᵟ";s:2:"δ";s:3:"ᵠ";s:2:"φ";s:3:"ᵡ";s:2:"χ";s:3:"ᵢ";s:1:"i";s:3:"ᵣ";s:1:"r";s:3:"ᵤ";s:1:"u";s:3:"ᵥ";s:1:"v";s:3:"ᵦ";s:2:"β";s:3:"ᵧ";s:2:"γ";s:3:"ᵨ";s:2:"ρ";s:3:"ᵩ";s:2:"φ";s:3:"ᵪ";s:2:"χ";s:3:"ᵸ";s:2:"н";s:3:"ᶛ";s:2:"ɒ";s:3:"ᶜ";s:1:"c";s:3:"ᶝ";s:2:"ɕ";s:3:"ᶞ";s:2:"ð";s:3:"ᶟ";s:2:"ɜ";s:3:"ᶠ";s:1:"f";s:3:"ᶡ";s:2:"ɟ";s:3:"ᶢ";s:2:"ɡ";s:3:"ᶣ";s:2:"ɥ";s:3:"ᶤ";s:2:"ɨ";s:3:"ᶥ";s:2:"ɩ";s:3:"ᶦ";s:2:"ɪ";s:3:"ᶧ";s:3:"ᵻ";s:3:"ᶨ";s:2:"ʝ";s:3:"ᶩ";s:2:"ɭ";s:3:"ᶪ";s:3:"ᶅ";s:3:"ᶫ";s:2:"ʟ";s:3:"ᶬ";s:2:"ɱ";s:3:"ᶭ";s:2:"ɰ";s:3:"ᶮ";s:2:"ɲ";s:3:"ᶯ";s:2:"ɳ";s:3:"ᶰ";s:2:"ɴ";s:3:"ᶱ";s:2:"ɵ";s:3:"ᶲ";s:2:"ɸ";s:3:"ᶳ";s:2:"ʂ";s:3:"ᶴ";s:2:"ʃ";s:3:"ᶵ";s:2:"ƫ";s:3:"ᶶ";s:2:"ʉ";s:3:"ᶷ";s:2:"ʊ";s:3:"ᶸ";s:3:"ᴜ";s:3:"ᶹ";s:2:"ʋ";s:3:"ᶺ";s:2:"ʌ";s:3:"ᶻ";s:1:"z";s:3:"ᶼ";s:2:"ʐ";s:3:"ᶽ";s:2:"ʑ";s:3:"ᶾ";s:2:"ʒ";s:3:"ᶿ";s:2:"θ";s:3:"Ḁ";s:3:"Ḁ";s:3:"ḁ";s:3:"ḁ";s:3:"Ḃ";s:3:"Ḃ";s:3:"ḃ";s:3:"ḃ";s:3:"Ḅ";s:3:"Ḅ";s:3:"ḅ";s:3:"ḅ";s:3:"Ḇ";s:3:"Ḇ";s:3:"ḇ";s:3:"ḇ";s:3:"Ḉ";s:5:"Ḉ";s:3:"ḉ";s:5:"ḉ";s:3:"Ḋ";s:3:"Ḋ";s:3:"ḋ";s:3:"ḋ";s:3:"Ḍ";s:3:"Ḍ";s:3:"ḍ";s:3:"ḍ";s:3:"Ḏ";s:3:"Ḏ";s:3:"ḏ";s:3:"ḏ";s:3:"Ḑ";s:3:"Ḑ";s:3:"ḑ";s:3:"ḑ";s:3:"Ḓ";s:3:"Ḓ";s:3:"ḓ";s:3:"ḓ";s:3:"Ḕ";s:5:"Ḕ";s:3:"ḕ";s:5:"ḕ";s:3:"Ḗ";s:5:"Ḗ";s:3:"ḗ";s:5:"ḗ";s:3:"Ḙ";s:3:"Ḙ";s:3:"ḙ";s:3:"ḙ";s:3:"Ḛ";s:3:"Ḛ";s:3:"ḛ";s:3:"ḛ";s:3:"Ḝ";s:5:"Ḝ";s:3:"ḝ";s:5:"ḝ";s:3:"Ḟ";s:3:"Ḟ";s:3:"ḟ";s:3:"ḟ";s:3:"Ḡ";s:3:"Ḡ";s:3:"ḡ";s:3:"ḡ";s:3:"Ḣ";s:3:"Ḣ";s:3:"ḣ";s:3:"ḣ";s:3:"Ḥ";s:3:"Ḥ";s:3:"ḥ";s:3:"ḥ";s:3:"Ḧ";s:3:"Ḧ";s:3:"ḧ";s:3:"ḧ";s:3:"Ḩ";s:3:"Ḩ";s:3:"ḩ";s:3:"ḩ";s:3:"Ḫ";s:3:"Ḫ";s:3:"ḫ";s:3:"ḫ";s:3:"Ḭ";s:3:"Ḭ";s:3:"ḭ";s:3:"ḭ";s:3:"Ḯ";s:5:"Ḯ";s:3:"ḯ";s:5:"ḯ";s:3:"Ḱ";s:3:"Ḱ";s:3:"ḱ";s:3:"ḱ";s:3:"Ḳ";s:3:"Ḳ";s:3:"ḳ";s:3:"ḳ";s:3:"Ḵ";s:3:"Ḵ";s:3:"ḵ";s:3:"ḵ";s:3:"Ḷ";s:3:"Ḷ";s:3:"ḷ";s:3:"ḷ";s:3:"Ḹ";s:5:"Ḹ";s:3:"ḹ";s:5:"ḹ";s:3:"Ḻ";s:3:"Ḻ";s:3:"ḻ";s:3:"ḻ";s:3:"Ḽ";s:3:"Ḽ";s:3:"ḽ";s:3:"ḽ";s:3:"Ḿ";s:3:"Ḿ";s:3:"ḿ";s:3:"ḿ";s:3:"Ṁ";s:3:"Ṁ";s:3:"ṁ";s:3:"ṁ";s:3:"Ṃ";s:3:"Ṃ";s:3:"ṃ";s:3:"ṃ";s:3:"Ṅ";s:3:"Ṅ";s:3:"ṅ";s:3:"ṅ";s:3:"Ṇ";s:3:"Ṇ";s:3:"ṇ";s:3:"ṇ";s:3:"Ṉ";s:3:"Ṉ";s:3:"ṉ";s:3:"ṉ";s:3:"Ṋ";s:3:"Ṋ";s:3:"ṋ";s:3:"ṋ";s:3:"Ṍ";s:5:"Ṍ";s:3:"ṍ";s:5:"ṍ";s:3:"Ṏ";s:5:"Ṏ";s:3:"ṏ";s:5:"ṏ";s:3:"Ṑ";s:5:"Ṑ";s:3:"ṑ";s:5:"ṑ";s:3:"Ṓ";s:5:"Ṓ";s:3:"ṓ";s:5:"ṓ";s:3:"Ṕ";s:3:"Ṕ";s:3:"ṕ";s:3:"ṕ";s:3:"Ṗ";s:3:"Ṗ";s:3:"ṗ";s:3:"ṗ";s:3:"Ṙ";s:3:"Ṙ";s:3:"ṙ";s:3:"ṙ";s:3:"Ṛ";s:3:"Ṛ";s:3:"ṛ";s:3:"ṛ";s:3:"Ṝ";s:5:"Ṝ";s:3:"ṝ";s:5:"ṝ";s:3:"Ṟ";s:3:"Ṟ";s:3:"ṟ";s:3:"ṟ";s:3:"Ṡ";s:3:"Ṡ";s:3:"ṡ";s:3:"ṡ";s:3:"Ṣ";s:3:"Ṣ";s:3:"ṣ";s:3:"ṣ";s:3:"Ṥ";s:5:"Ṥ";s:3:"ṥ";s:5:"ṥ";s:3:"Ṧ";s:5:"Ṧ";s:3:"ṧ";s:5:"ṧ";s:3:"Ṩ";s:5:"Ṩ";s:3:"ṩ";s:5:"ṩ";s:3:"Ṫ";s:3:"Ṫ";s:3:"ṫ";s:3:"ṫ";s:3:"Ṭ";s:3:"Ṭ";s:3:"ṭ";s:3:"ṭ";s:3:"Ṯ";s:3:"Ṯ";s:3:"ṯ";s:3:"ṯ";s:3:"Ṱ";s:3:"Ṱ";s:3:"ṱ";s:3:"ṱ";s:3:"Ṳ";s:3:"Ṳ";s:3:"ṳ";s:3:"ṳ";s:3:"Ṵ";s:3:"Ṵ";s:3:"ṵ";s:3:"ṵ";s:3:"Ṷ";s:3:"Ṷ";s:3:"ṷ";s:3:"ṷ";s:3:"Ṹ";s:5:"Ṹ";s:3:"ṹ";s:5:"ṹ";s:3:"Ṻ";s:5:"Ṻ";s:3:"ṻ";s:5:"ṻ";s:3:"Ṽ";s:3:"Ṽ";s:3:"ṽ";s:3:"ṽ";s:3:"Ṿ";s:3:"Ṿ";s:3:"ṿ";s:3:"ṿ";s:3:"Ẁ";s:3:"Ẁ";s:3:"ẁ";s:3:"ẁ";s:3:"Ẃ";s:3:"Ẃ";s:3:"ẃ";s:3:"ẃ";s:3:"Ẅ";s:3:"Ẅ";s:3:"ẅ";s:3:"ẅ";s:3:"Ẇ";s:3:"Ẇ";s:3:"ẇ";s:3:"ẇ";s:3:"Ẉ";s:3:"Ẉ";s:3:"ẉ";s:3:"ẉ";s:3:"Ẋ";s:3:"Ẋ";s:3:"ẋ";s:3:"ẋ";s:3:"Ẍ";s:3:"Ẍ";s:3:"ẍ";s:3:"ẍ";s:3:"Ẏ";s:3:"Ẏ";s:3:"ẏ";s:3:"ẏ";s:3:"Ẑ";s:3:"Ẑ";s:3:"ẑ";s:3:"ẑ";s:3:"Ẓ";s:3:"Ẓ";s:3:"ẓ";s:3:"ẓ";s:3:"Ẕ";s:3:"Ẕ";s:3:"ẕ";s:3:"ẕ";s:3:"ẖ";s:3:"ẖ";s:3:"ẗ";s:3:"ẗ";s:3:"ẘ";s:3:"ẘ";s:3:"ẙ";s:3:"ẙ";s:3:"ẚ";s:3:"aʾ";s:3:"ẛ";s:3:"ṡ";s:3:"Ạ";s:3:"Ạ";s:3:"ạ";s:3:"ạ";s:3:"Ả";s:3:"Ả";s:3:"ả";s:3:"ả";s:3:"Ấ";s:5:"Ấ";s:3:"ấ";s:5:"ấ";s:3:"Ầ";s:5:"Ầ";s:3:"ầ";s:5:"ầ";s:3:"Ẩ";s:5:"Ẩ";s:3:"ẩ";s:5:"ẩ";s:3:"Ẫ";s:5:"Ẫ";s:3:"ẫ";s:5:"ẫ";s:3:"Ậ";s:5:"Ậ";s:3:"ậ";s:5:"ậ";s:3:"Ắ";s:5:"Ắ";s:3:"ắ";s:5:"ắ";s:3:"Ằ";s:5:"Ằ";s:3:"ằ";s:5:"ằ";s:3:"Ẳ";s:5:"Ẳ";s:3:"ẳ";s:5:"ẳ";s:3:"Ẵ";s:5:"Ẵ";s:3:"ẵ";s:5:"ẵ";s:3:"Ặ";s:5:"Ặ";s:3:"ặ";s:5:"ặ";s:3:"Ẹ";s:3:"Ẹ";s:3:"ẹ";s:3:"ẹ";s:3:"Ẻ";s:3:"Ẻ";s:3:"ẻ";s:3:"ẻ";s:3:"Ẽ";s:3:"Ẽ";s:3:"ẽ";s:3:"ẽ";s:3:"Ế";s:5:"Ế";s:3:"ế";s:5:"ế";s:3:"Ề";s:5:"Ề";s:3:"ề";s:5:"ề";s:3:"Ể";s:5:"Ể";s:3:"ể";s:5:"ể";s:3:"Ễ";s:5:"Ễ";s:3:"ễ";s:5:"ễ";s:3:"Ệ";s:5:"Ệ";s:3:"ệ";s:5:"ệ";s:3:"Ỉ";s:3:"Ỉ";s:3:"ỉ";s:3:"ỉ";s:3:"Ị";s:3:"Ị";s:3:"ị";s:3:"ị";s:3:"Ọ";s:3:"Ọ";s:3:"ọ";s:3:"ọ";s:3:"Ỏ";s:3:"Ỏ";s:3:"ỏ";s:3:"ỏ";s:3:"Ố";s:5:"Ố";s:3:"ố";s:5:"ố";s:3:"Ồ";s:5:"Ồ";s:3:"ồ";s:5:"ồ";s:3:"Ổ";s:5:"Ổ";s:3:"ổ";s:5:"ổ";s:3:"Ỗ";s:5:"Ỗ";s:3:"ỗ";s:5:"ỗ";s:3:"Ộ";s:5:"Ộ";s:3:"ộ";s:5:"ộ";s:3:"Ớ";s:5:"Ớ";s:3:"ớ";s:5:"ớ";s:3:"Ờ";s:5:"Ờ";s:3:"ờ";s:5:"ờ";s:3:"Ở";s:5:"Ở";s:3:"ở";s:5:"ở";s:3:"Ỡ";s:5:"Ỡ";s:3:"ỡ";s:5:"ỡ";s:3:"Ợ";s:5:"Ợ";s:3:"ợ";s:5:"ợ";s:3:"Ụ";s:3:"Ụ";s:3:"ụ";s:3:"ụ";s:3:"Ủ";s:3:"Ủ";s:3:"ủ";s:3:"ủ";s:3:"Ứ";s:5:"Ứ";s:3:"ứ";s:5:"ứ";s:3:"Ừ";s:5:"Ừ";s:3:"ừ";s:5:"ừ";s:3:"Ử";s:5:"Ử";s:3:"ử";s:5:"ử";s:3:"Ữ";s:5:"Ữ";s:3:"ữ";s:5:"ữ";s:3:"Ự";s:5:"Ự";s:3:"ự";s:5:"ự";s:3:"Ỳ";s:3:"Ỳ";s:3:"ỳ";s:3:"ỳ";s:3:"Ỵ";s:3:"Ỵ";s:3:"ỵ";s:3:"ỵ";s:3:"Ỷ";s:3:"Ỷ";s:3:"ỷ";s:3:"ỷ";s:3:"Ỹ";s:3:"Ỹ";s:3:"ỹ";s:3:"ỹ";s:3:"ἀ";s:4:"ἀ";s:3:"ἁ";s:4:"ἁ";s:3:"ἂ";s:6:"ἂ";s:3:"ἃ";s:6:"ἃ";s:3:"ἄ";s:6:"ἄ";s:3:"ἅ";s:6:"ἅ";s:3:"ἆ";s:6:"ἆ";s:3:"ἇ";s:6:"ἇ";s:3:"Ἀ";s:4:"Ἀ";s:3:"Ἁ";s:4:"Ἁ";s:3:"Ἂ";s:6:"Ἂ";s:3:"Ἃ";s:6:"Ἃ";s:3:"Ἄ";s:6:"Ἄ";s:3:"Ἅ";s:6:"Ἅ";s:3:"Ἆ";s:6:"Ἆ";s:3:"Ἇ";s:6:"Ἇ";s:3:"ἐ";s:4:"ἐ";s:3:"ἑ";s:4:"ἑ";s:3:"ἒ";s:6:"ἒ";s:3:"ἓ";s:6:"ἓ";s:3:"ἔ";s:6:"ἔ";s:3:"ἕ";s:6:"ἕ";s:3:"Ἐ";s:4:"Ἐ";s:3:"Ἑ";s:4:"Ἑ";s:3:"Ἒ";s:6:"Ἒ";s:3:"Ἓ";s:6:"Ἓ";s:3:"Ἔ";s:6:"Ἔ";s:3:"Ἕ";s:6:"Ἕ";s:3:"ἠ";s:4:"ἠ";s:3:"ἡ";s:4:"ἡ";s:3:"ἢ";s:6:"ἢ";s:3:"ἣ";s:6:"ἣ";s:3:"ἤ";s:6:"ἤ";s:3:"ἥ";s:6:"ἥ";s:3:"ἦ";s:6:"ἦ";s:3:"ἧ";s:6:"ἧ";s:3:"Ἠ";s:4:"Ἠ";s:3:"Ἡ";s:4:"Ἡ";s:3:"Ἢ";s:6:"Ἢ";s:3:"Ἣ";s:6:"Ἣ";s:3:"Ἤ";s:6:"Ἤ";s:3:"Ἥ";s:6:"Ἥ";s:3:"Ἦ";s:6:"Ἦ";s:3:"Ἧ";s:6:"Ἧ";s:3:"ἰ";s:4:"ἰ";s:3:"ἱ";s:4:"ἱ";s:3:"ἲ";s:6:"ἲ";s:3:"ἳ";s:6:"ἳ";s:3:"ἴ";s:6:"ἴ";s:3:"ἵ";s:6:"ἵ";s:3:"ἶ";s:6:"ἶ";s:3:"ἷ";s:6:"ἷ";s:3:"Ἰ";s:4:"Ἰ";s:3:"Ἱ";s:4:"Ἱ";s:3:"Ἲ";s:6:"Ἲ";s:3:"Ἳ";s:6:"Ἳ";s:3:"Ἴ";s:6:"Ἴ";s:3:"Ἵ";s:6:"Ἵ";s:3:"Ἶ";s:6:"Ἶ";s:3:"Ἷ";s:6:"Ἷ";s:3:"ὀ";s:4:"ὀ";s:3:"ὁ";s:4:"ὁ";s:3:"ὂ";s:6:"ὂ";s:3:"ὃ";s:6:"ὃ";s:3:"ὄ";s:6:"ὄ";s:3:"ὅ";s:6:"ὅ";s:3:"Ὀ";s:4:"Ὀ";s:3:"Ὁ";s:4:"Ὁ";s:3:"Ὂ";s:6:"Ὂ";s:3:"Ὃ";s:6:"Ὃ";s:3:"Ὄ";s:6:"Ὄ";s:3:"Ὅ";s:6:"Ὅ";s:3:"ὐ";s:4:"ὐ";s:3:"ὑ";s:4:"ὑ";s:3:"ὒ";s:6:"ὒ";s:3:"ὓ";s:6:"ὓ";s:3:"ὔ";s:6:"ὔ";s:3:"ὕ";s:6:"ὕ";s:3:"ὖ";s:6:"ὖ";s:3:"ὗ";s:6:"ὗ";s:3:"Ὑ";s:4:"Ὑ";s:3:"Ὓ";s:6:"Ὓ";s:3:"Ὕ";s:6:"Ὕ";s:3:"Ὗ";s:6:"Ὗ";s:3:"ὠ";s:4:"ὠ";s:3:"ὡ";s:4:"ὡ";s:3:"ὢ";s:6:"ὢ";s:3:"ὣ";s:6:"ὣ";s:3:"ὤ";s:6:"ὤ";s:3:"ὥ";s:6:"ὥ";s:3:"ὦ";s:6:"ὦ";s:3:"ὧ";s:6:"ὧ";s:3:"Ὠ";s:4:"Ὠ";s:3:"Ὡ";s:4:"Ὡ";s:3:"Ὢ";s:6:"Ὢ";s:3:"Ὣ";s:6:"Ὣ";s:3:"Ὤ";s:6:"Ὤ";s:3:"Ὥ";s:6:"Ὥ";s:3:"Ὦ";s:6:"Ὦ";s:3:"Ὧ";s:6:"Ὧ";s:3:"ὰ";s:4:"ὰ";s:3:"ά";s:4:"ά";s:3:"ὲ";s:4:"ὲ";s:3:"έ";s:4:"έ";s:3:"ὴ";s:4:"ὴ";s:3:"ή";s:4:"ή";s:3:"ὶ";s:4:"ὶ";s:3:"ί";s:4:"ί";s:3:"ὸ";s:4:"ὸ";s:3:"ό";s:4:"ό";s:3:"ὺ";s:4:"ὺ";s:3:"ύ";s:4:"ύ";s:3:"ὼ";s:4:"ὼ";s:3:"ώ";s:4:"ώ";s:3:"ᾀ";s:6:"ᾀ";s:3:"ᾁ";s:6:"ᾁ";s:3:"ᾂ";s:8:"ᾂ";s:3:"ᾃ";s:8:"ᾃ";s:3:"ᾄ";s:8:"ᾄ";s:3:"ᾅ";s:8:"ᾅ";s:3:"ᾆ";s:8:"ᾆ";s:3:"ᾇ";s:8:"ᾇ";s:3:"ᾈ";s:6:"ᾈ";s:3:"ᾉ";s:6:"ᾉ";s:3:"ᾊ";s:8:"ᾊ";s:3:"ᾋ";s:8:"ᾋ";s:3:"ᾌ";s:8:"ᾌ";s:3:"ᾍ";s:8:"ᾍ";s:3:"ᾎ";s:8:"ᾎ";s:3:"ᾏ";s:8:"ᾏ";s:3:"ᾐ";s:6:"ᾐ";s:3:"ᾑ";s:6:"ᾑ";s:3:"ᾒ";s:8:"ᾒ";s:3:"ᾓ";s:8:"ᾓ";s:3:"ᾔ";s:8:"ᾔ";s:3:"ᾕ";s:8:"ᾕ";s:3:"ᾖ";s:8:"ᾖ";s:3:"ᾗ";s:8:"ᾗ";s:3:"ᾘ";s:6:"ᾘ";s:3:"ᾙ";s:6:"ᾙ";s:3:"ᾚ";s:8:"ᾚ";s:3:"ᾛ";s:8:"ᾛ";s:3:"ᾜ";s:8:"ᾜ";s:3:"ᾝ";s:8:"ᾝ";s:3:"ᾞ";s:8:"ᾞ";s:3:"ᾟ";s:8:"ᾟ";s:3:"ᾠ";s:6:"ᾠ";s:3:"ᾡ";s:6:"ᾡ";s:3:"ᾢ";s:8:"ᾢ";s:3:"ᾣ";s:8:"ᾣ";s:3:"ᾤ";s:8:"ᾤ";s:3:"ᾥ";s:8:"ᾥ";s:3:"ᾦ";s:8:"ᾦ";s:3:"ᾧ";s:8:"ᾧ";s:3:"ᾨ";s:6:"ᾨ";s:3:"ᾩ";s:6:"ᾩ";s:3:"ᾪ";s:8:"ᾪ";s:3:"ᾫ";s:8:"ᾫ";s:3:"ᾬ";s:8:"ᾬ";s:3:"ᾭ";s:8:"ᾭ";s:3:"ᾮ";s:8:"ᾮ";s:3:"ᾯ";s:8:"ᾯ";s:3:"ᾰ";s:4:"ᾰ";s:3:"ᾱ";s:4:"ᾱ";s:3:"ᾲ";s:6:"ᾲ";s:3:"ᾳ";s:4:"ᾳ";s:3:"ᾴ";s:6:"ᾴ";s:3:"ᾶ";s:4:"ᾶ";s:3:"ᾷ";s:6:"ᾷ";s:3:"Ᾰ";s:4:"Ᾰ";s:3:"Ᾱ";s:4:"Ᾱ";s:3:"Ὰ";s:4:"Ὰ";s:3:"Ά";s:4:"Ά";s:3:"ᾼ";s:4:"ᾼ";s:3:"᾽";s:3:" ̓";s:3:"ι";s:2:"ι";s:3:"᾿";s:3:" ̓";s:3:"῀";s:3:" ͂";s:3:"῁";s:5:" ̈͂";s:3:"ῂ";s:6:"ῂ";s:3:"ῃ";s:4:"ῃ";s:3:"ῄ";s:6:"ῄ";s:3:"ῆ";s:4:"ῆ";s:3:"ῇ";s:6:"ῇ";s:3:"Ὲ";s:4:"Ὲ";s:3:"Έ";s:4:"Έ";s:3:"Ὴ";s:4:"Ὴ";s:3:"Ή";s:4:"Ή";s:3:"ῌ";s:4:"ῌ";s:3:"῍";s:5:" ̓̀";s:3:"῎";s:5:" ̓́";s:3:"῏";s:5:" ̓͂";s:3:"ῐ";s:4:"ῐ";s:3:"ῑ";s:4:"ῑ";s:3:"ῒ";s:6:"ῒ";s:3:"ΐ";s:6:"ΐ";s:3:"ῖ";s:4:"ῖ";s:3:"ῗ";s:6:"ῗ";s:3:"Ῐ";s:4:"Ῐ";s:3:"Ῑ";s:4:"Ῑ";s:3:"Ὶ";s:4:"Ὶ";s:3:"Ί";s:4:"Ί";s:3:"῝";s:5:" ̔̀";s:3:"῞";s:5:" ̔́";s:3:"῟";s:5:" ̔͂";s:3:"ῠ";s:4:"ῠ";s:3:"ῡ";s:4:"ῡ";s:3:"ῢ";s:6:"ῢ";s:3:"ΰ";s:6:"ΰ";s:3:"ῤ";s:4:"ῤ";s:3:"ῥ";s:4:"ῥ";s:3:"ῦ";s:4:"ῦ";s:3:"ῧ";s:6:"ῧ";s:3:"Ῠ";s:4:"Ῠ";s:3:"Ῡ";s:4:"Ῡ";s:3:"Ὺ";s:4:"Ὺ";s:3:"Ύ";s:4:"Ύ";s:3:"Ῥ";s:4:"Ῥ";s:3:"῭";s:5:" ̈̀";s:3:"΅";s:5:" ̈́";s:3:"`";s:1:"`";s:3:"ῲ";s:6:"ῲ";s:3:"ῳ";s:4:"ῳ";s:3:"ῴ";s:6:"ῴ";s:3:"ῶ";s:4:"ῶ";s:3:"ῷ";s:6:"ῷ";s:3:"Ὸ";s:4:"Ὸ";s:3:"Ό";s:4:"Ό";s:3:"Ὼ";s:4:"Ὼ";s:3:"Ώ";s:4:"Ώ";s:3:"ῼ";s:4:"ῼ";s:3:"´";s:3:" ́";s:3:"῾";s:3:" ̔";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:" ";s:1:" ";s:3:"‑";s:3:"‐";s:3:"‗";s:3:" ̳";s:3:"․";s:1:".";s:3:"‥";s:2:"..";s:3:"…";s:3:"...";s:3:" ";s:1:" ";s:3:"″";s:6:"′′";s:3:"‴";s:9:"′′′";s:3:"‶";s:6:"‵‵";s:3:"‷";s:9:"‵‵‵";s:3:"‼";s:2:"!!";s:3:"‾";s:3:" ̅";s:3:"⁇";s:2:"??";s:3:"⁈";s:2:"?!";s:3:"⁉";s:2:"!?";s:3:"⁗";s:12:"′′′′";s:3:" ";s:1:" ";s:3:"⁰";s:1:"0";s:3:"ⁱ";s:1:"i";s:3:"⁴";s:1:"4";s:3:"⁵";s:1:"5";s:3:"⁶";s:1:"6";s:3:"⁷";s:1:"7";s:3:"⁸";s:1:"8";s:3:"⁹";s:1:"9";s:3:"⁺";s:1:"+";s:3:"⁻";s:3:"−";s:3:"⁼";s:1:"=";s:3:"⁽";s:1:"(";s:3:"⁾";s:1:")";s:3:"ⁿ";s:1:"n";s:3:"₀";s:1:"0";s:3:"₁";s:1:"1";s:3:"₂";s:1:"2";s:3:"₃";s:1:"3";s:3:"₄";s:1:"4";s:3:"₅";s:1:"5";s:3:"₆";s:1:"6";s:3:"₇";s:1:"7";s:3:"₈";s:1:"8";s:3:"₉";s:1:"9";s:3:"₊";s:1:"+";s:3:"₋";s:3:"−";s:3:"₌";s:1:"=";s:3:"₍";s:1:"(";s:3:"₎";s:1:")";s:3:"ₐ";s:1:"a";s:3:"ₑ";s:1:"e";s:3:"ₒ";s:1:"o";s:3:"ₓ";s:1:"x";s:3:"ₔ";s:2:"ə";s:3:"ₕ";s:1:"h";s:3:"ₖ";s:1:"k";s:3:"ₗ";s:1:"l";s:3:"ₘ";s:1:"m";s:3:"ₙ";s:1:"n";s:3:"ₚ";s:1:"p";s:3:"ₛ";s:1:"s";s:3:"ₜ";s:1:"t";s:3:"₨";s:2:"Rs";s:3:"℀";s:3:"a/c";s:3:"℁";s:3:"a/s";s:3:"ℂ";s:1:"C";s:3:"℃";s:3:"°C";s:3:"℅";s:3:"c/o";s:3:"℆";s:3:"c/u";s:3:"ℇ";s:2:"Ɛ";s:3:"℉";s:3:"°F";s:3:"ℊ";s:1:"g";s:3:"ℋ";s:1:"H";s:3:"ℌ";s:1:"H";s:3:"ℍ";s:1:"H";s:3:"ℎ";s:1:"h";s:3:"ℏ";s:2:"ħ";s:3:"ℐ";s:1:"I";s:3:"ℑ";s:1:"I";s:3:"ℒ";s:1:"L";s:3:"ℓ";s:1:"l";s:3:"ℕ";s:1:"N";s:3:"№";s:2:"No";s:3:"ℙ";s:1:"P";s:3:"ℚ";s:1:"Q";s:3:"ℛ";s:1:"R";s:3:"ℜ";s:1:"R";s:3:"ℝ";s:1:"R";s:3:"℠";s:2:"SM";s:3:"℡";s:3:"TEL";s:3:"™";s:2:"TM";s:3:"ℤ";s:1:"Z";s:3:"Ω";s:2:"Ω";s:3:"ℨ";s:1:"Z";s:3:"K";s:1:"K";s:3:"Å";s:3:"Å";s:3:"ℬ";s:1:"B";s:3:"ℭ";s:1:"C";s:3:"ℯ";s:1:"e";s:3:"ℰ";s:1:"E";s:3:"ℱ";s:1:"F";s:3:"ℳ";s:1:"M";s:3:"ℴ";s:1:"o";s:3:"ℵ";s:2:"א";s:3:"ℶ";s:2:"ב";s:3:"ℷ";s:2:"ג";s:3:"ℸ";s:2:"ד";s:3:"ℹ";s:1:"i";s:3:"℻";s:3:"FAX";s:3:"ℼ";s:2:"π";s:3:"ℽ";s:2:"γ";s:3:"ℾ";s:2:"Γ";s:3:"ℿ";s:2:"Π";s:3:"⅀";s:3:"∑";s:3:"ⅅ";s:1:"D";s:3:"ⅆ";s:1:"d";s:3:"ⅇ";s:1:"e";s:3:"ⅈ";s:1:"i";s:3:"ⅉ";s:1:"j";s:3:"⅐";s:5:"1⁄7";s:3:"⅑";s:5:"1⁄9";s:3:"⅒";s:6:"1⁄10";s:3:"⅓";s:5:"1⁄3";s:3:"⅔";s:5:"2⁄3";s:3:"⅕";s:5:"1⁄5";s:3:"⅖";s:5:"2⁄5";s:3:"⅗";s:5:"3⁄5";s:3:"⅘";s:5:"4⁄5";s:3:"⅙";s:5:"1⁄6";s:3:"⅚";s:5:"5⁄6";s:3:"⅛";s:5:"1⁄8";s:3:"⅜";s:5:"3⁄8";s:3:"⅝";s:5:"5⁄8";s:3:"⅞";s:5:"7⁄8";s:3:"⅟";s:4:"1⁄";s:3:"Ⅰ";s:1:"I";s:3:"Ⅱ";s:2:"II";s:3:"Ⅲ";s:3:"III";s:3:"Ⅳ";s:2:"IV";s:3:"Ⅴ";s:1:"V";s:3:"Ⅵ";s:2:"VI";s:3:"Ⅶ";s:3:"VII";s:3:"Ⅷ";s:4:"VIII";s:3:"Ⅸ";s:2:"IX";s:3:"Ⅹ";s:1:"X";s:3:"Ⅺ";s:2:"XI";s:3:"Ⅻ";s:3:"XII";s:3:"Ⅼ";s:1:"L";s:3:"Ⅽ";s:1:"C";s:3:"Ⅾ";s:1:"D";s:3:"Ⅿ";s:1:"M";s:3:"ⅰ";s:1:"i";s:3:"ⅱ";s:2:"ii";s:3:"ⅲ";s:3:"iii";s:3:"ⅳ";s:2:"iv";s:3:"ⅴ";s:1:"v";s:3:"ⅵ";s:2:"vi";s:3:"ⅶ";s:3:"vii";s:3:"ⅷ";s:4:"viii";s:3:"ⅸ";s:2:"ix";s:3:"ⅹ";s:1:"x";s:3:"ⅺ";s:2:"xi";s:3:"ⅻ";s:3:"xii";s:3:"ⅼ";s:1:"l";s:3:"ⅽ";s:1:"c";s:3:"ⅾ";s:1:"d";s:3:"ⅿ";s:1:"m";s:3:"↉";s:5:"0⁄3";s:3:"↚";s:5:"↚";s:3:"↛";s:5:"↛";s:3:"↮";s:5:"↮";s:3:"⇍";s:5:"⇍";s:3:"⇎";s:5:"⇎";s:3:"⇏";s:5:"⇏";s:3:"∄";s:5:"∄";s:3:"∉";s:5:"∉";s:3:"∌";s:5:"∌";s:3:"∤";s:5:"∤";s:3:"∦";s:5:"∦";s:3:"∬";s:6:"∫∫";s:3:"∭";s:9:"∫∫∫";s:3:"∯";s:6:"∮∮";s:3:"∰";s:9:"∮∮∮";s:3:"≁";s:5:"≁";s:3:"≄";s:5:"≄";s:3:"≇";s:5:"≇";s:3:"≉";s:5:"≉";s:3:"≠";s:3:"≠";s:3:"≢";s:5:"≢";s:3:"≭";s:5:"≭";s:3:"≮";s:3:"≮";s:3:"≯";s:3:"≯";s:3:"≰";s:5:"≰";s:3:"≱";s:5:"≱";s:3:"≴";s:5:"≴";s:3:"≵";s:5:"≵";s:3:"≸";s:5:"≸";s:3:"≹";s:5:"≹";s:3:"⊀";s:5:"⊀";s:3:"⊁";s:5:"⊁";s:3:"⊄";s:5:"⊄";s:3:"⊅";s:5:"⊅";s:3:"⊈";s:5:"⊈";s:3:"⊉";s:5:"⊉";s:3:"⊬";s:5:"⊬";s:3:"⊭";s:5:"⊭";s:3:"⊮";s:5:"⊮";s:3:"⊯";s:5:"⊯";s:3:"⋠";s:5:"⋠";s:3:"⋡";s:5:"⋡";s:3:"⋢";s:5:"⋢";s:3:"⋣";s:5:"⋣";s:3:"⋪";s:5:"⋪";s:3:"⋫";s:5:"⋫";s:3:"⋬";s:5:"⋬";s:3:"⋭";s:5:"⋭";s:3:"〈";s:3:"〈";s:3:"〉";s:3:"〉";s:3:"①";s:1:"1";s:3:"②";s:1:"2";s:3:"③";s:1:"3";s:3:"④";s:1:"4";s:3:"⑤";s:1:"5";s:3:"⑥";s:1:"6";s:3:"⑦";s:1:"7";s:3:"⑧";s:1:"8";s:3:"⑨";s:1:"9";s:3:"⑩";s:2:"10";s:3:"⑪";s:2:"11";s:3:"⑫";s:2:"12";s:3:"⑬";s:2:"13";s:3:"⑭";s:2:"14";s:3:"⑮";s:2:"15";s:3:"⑯";s:2:"16";s:3:"⑰";s:2:"17";s:3:"⑱";s:2:"18";s:3:"⑲";s:2:"19";s:3:"⑳";s:2:"20";s:3:"⑴";s:3:"(1)";s:3:"⑵";s:3:"(2)";s:3:"⑶";s:3:"(3)";s:3:"⑷";s:3:"(4)";s:3:"⑸";s:3:"(5)";s:3:"⑹";s:3:"(6)";s:3:"⑺";s:3:"(7)";s:3:"⑻";s:3:"(8)";s:3:"⑼";s:3:"(9)";s:3:"⑽";s:4:"(10)";s:3:"⑾";s:4:"(11)";s:3:"⑿";s:4:"(12)";s:3:"⒀";s:4:"(13)";s:3:"⒁";s:4:"(14)";s:3:"⒂";s:4:"(15)";s:3:"⒃";s:4:"(16)";s:3:"⒄";s:4:"(17)";s:3:"⒅";s:4:"(18)";s:3:"⒆";s:4:"(19)";s:3:"⒇";s:4:"(20)";s:3:"⒈";s:2:"1.";s:3:"⒉";s:2:"2.";s:3:"⒊";s:2:"3.";s:3:"⒋";s:2:"4.";s:3:"⒌";s:2:"5.";s:3:"⒍";s:2:"6.";s:3:"⒎";s:2:"7.";s:3:"⒏";s:2:"8.";s:3:"⒐";s:2:"9.";s:3:"⒑";s:3:"10.";s:3:"⒒";s:3:"11.";s:3:"⒓";s:3:"12.";s:3:"⒔";s:3:"13.";s:3:"⒕";s:3:"14.";s:3:"⒖";s:3:"15.";s:3:"⒗";s:3:"16.";s:3:"⒘";s:3:"17.";s:3:"⒙";s:3:"18.";s:3:"⒚";s:3:"19.";s:3:"⒛";s:3:"20.";s:3:"⒜";s:3:"(a)";s:3:"⒝";s:3:"(b)";s:3:"⒞";s:3:"(c)";s:3:"⒟";s:3:"(d)";s:3:"⒠";s:3:"(e)";s:3:"⒡";s:3:"(f)";s:3:"⒢";s:3:"(g)";s:3:"⒣";s:3:"(h)";s:3:"⒤";s:3:"(i)";s:3:"⒥";s:3:"(j)";s:3:"⒦";s:3:"(k)";s:3:"⒧";s:3:"(l)";s:3:"⒨";s:3:"(m)";s:3:"⒩";s:3:"(n)";s:3:"⒪";s:3:"(o)";s:3:"⒫";s:3:"(p)";s:3:"⒬";s:3:"(q)";s:3:"⒭";s:3:"(r)";s:3:"⒮";s:3:"(s)";s:3:"⒯";s:3:"(t)";s:3:"⒰";s:3:"(u)";s:3:"⒱";s:3:"(v)";s:3:"⒲";s:3:"(w)";s:3:"⒳";s:3:"(x)";s:3:"⒴";s:3:"(y)";s:3:"⒵";s:3:"(z)";s:3:"Ⓐ";s:1:"A";s:3:"Ⓑ";s:1:"B";s:3:"Ⓒ";s:1:"C";s:3:"Ⓓ";s:1:"D";s:3:"Ⓔ";s:1:"E";s:3:"Ⓕ";s:1:"F";s:3:"Ⓖ";s:1:"G";s:3:"Ⓗ";s:1:"H";s:3:"Ⓘ";s:1:"I";s:3:"Ⓙ";s:1:"J";s:3:"Ⓚ";s:1:"K";s:3:"Ⓛ";s:1:"L";s:3:"Ⓜ";s:1:"M";s:3:"Ⓝ";s:1:"N";s:3:"Ⓞ";s:1:"O";s:3:"Ⓟ";s:1:"P";s:3:"Ⓠ";s:1:"Q";s:3:"Ⓡ";s:1:"R";s:3:"Ⓢ";s:1:"S";s:3:"Ⓣ";s:1:"T";s:3:"Ⓤ";s:1:"U";s:3:"Ⓥ";s:1:"V";s:3:"Ⓦ";s:1:"W";s:3:"Ⓧ";s:1:"X";s:3:"Ⓨ";s:1:"Y";s:3:"Ⓩ";s:1:"Z";s:3:"ⓐ";s:1:"a";s:3:"ⓑ";s:1:"b";s:3:"ⓒ";s:1:"c";s:3:"ⓓ";s:1:"d";s:3:"ⓔ";s:1:"e";s:3:"ⓕ";s:1:"f";s:3:"ⓖ";s:1:"g";s:3:"ⓗ";s:1:"h";s:3:"ⓘ";s:1:"i";s:3:"ⓙ";s:1:"j";s:3:"ⓚ";s:1:"k";s:3:"ⓛ";s:1:"l";s:3:"ⓜ";s:1:"m";s:3:"ⓝ";s:1:"n";s:3:"ⓞ";s:1:"o";s:3:"ⓟ";s:1:"p";s:3:"ⓠ";s:1:"q";s:3:"ⓡ";s:1:"r";s:3:"ⓢ";s:1:"s";s:3:"ⓣ";s:1:"t";s:3:"ⓤ";s:1:"u";s:3:"ⓥ";s:1:"v";s:3:"ⓦ";s:1:"w";s:3:"ⓧ";s:1:"x";s:3:"ⓨ";s:1:"y";s:3:"ⓩ";s:1:"z";s:3:"⓪";s:1:"0";s:3:"⨌";s:12:"∫∫∫∫";s:3:"⩴";s:3:"::=";s:3:"⩵";s:2:"==";s:3:"⩶";s:3:"===";s:3:"⫝̸";s:5:"⫝̸";s:3:"ⱼ";s:1:"j";s:3:"ⱽ";s:1:"V";s:3:"ⵯ";s:3:"ⵡ";s:3:"⺟";s:3:"母";s:3:"⻳";s:3:"龟";s:3:"⼀";s:3:"一";s:3:"⼁";s:3:"丨";s:3:"⼂";s:3:"丶";s:3:"⼃";s:3:"丿";s:3:"⼄";s:3:"乙";s:3:"⼅";s:3:"亅";s:3:"⼆";s:3:"二";s:3:"⼇";s:3:"亠";s:3:"⼈";s:3:"人";s:3:"⼉";s:3:"儿";s:3:"⼊";s:3:"入";s:3:"⼋";s:3:"八";s:3:"⼌";s:3:"冂";s:3:"⼍";s:3:"冖";s:3:"⼎";s:3:"冫";s:3:"⼏";s:3:"几";s:3:"⼐";s:3:"凵";s:3:"⼑";s:3:"刀";s:3:"⼒";s:3:"力";s:3:"⼓";s:3:"勹";s:3:"⼔";s:3:"匕";s:3:"⼕";s:3:"匚";s:3:"⼖";s:3:"匸";s:3:"⼗";s:3:"十";s:3:"⼘";s:3:"卜";s:3:"⼙";s:3:"卩";s:3:"⼚";s:3:"厂";s:3:"⼛";s:3:"厶";s:3:"⼜";s:3:"又";s:3:"⼝";s:3:"口";s:3:"⼞";s:3:"囗";s:3:"⼟";s:3:"土";s:3:"⼠";s:3:"士";s:3:"⼡";s:3:"夂";s:3:"⼢";s:3:"夊";s:3:"⼣";s:3:"夕";s:3:"⼤";s:3:"大";s:3:"⼥";s:3:"女";s:3:"⼦";s:3:"子";s:3:"⼧";s:3:"宀";s:3:"⼨";s:3:"寸";s:3:"⼩";s:3:"小";s:3:"⼪";s:3:"尢";s:3:"⼫";s:3:"尸";s:3:"⼬";s:3:"屮";s:3:"⼭";s:3:"山";s:3:"⼮";s:3:"巛";s:3:"⼯";s:3:"工";s:3:"⼰";s:3:"己";s:3:"⼱";s:3:"巾";s:3:"⼲";s:3:"干";s:3:"⼳";s:3:"幺";s:3:"⼴";s:3:"广";s:3:"⼵";s:3:"廴";s:3:"⼶";s:3:"廾";s:3:"⼷";s:3:"弋";s:3:"⼸";s:3:"弓";s:3:"⼹";s:3:"彐";s:3:"⼺";s:3:"彡";s:3:"⼻";s:3:"彳";s:3:"⼼";s:3:"心";s:3:"⼽";s:3:"戈";s:3:"⼾";s:3:"戶";s:3:"⼿";s:3:"手";s:3:"⽀";s:3:"支";s:3:"⽁";s:3:"攴";s:3:"⽂";s:3:"文";s:3:"⽃";s:3:"斗";s:3:"⽄";s:3:"斤";s:3:"⽅";s:3:"方";s:3:"⽆";s:3:"无";s:3:"⽇";s:3:"日";s:3:"⽈";s:3:"曰";s:3:"⽉";s:3:"月";s:3:"⽊";s:3:"木";s:3:"⽋";s:3:"欠";s:3:"⽌";s:3:"止";s:3:"⽍";s:3:"歹";s:3:"⽎";s:3:"殳";s:3:"⽏";s:3:"毋";s:3:"⽐";s:3:"比";s:3:"⽑";s:3:"毛";s:3:"⽒";s:3:"氏";s:3:"⽓";s:3:"气";s:3:"⽔";s:3:"水";s:3:"⽕";s:3:"火";s:3:"⽖";s:3:"爪";s:3:"⽗";s:3:"父";s:3:"⽘";s:3:"爻";s:3:"⽙";s:3:"爿";s:3:"⽚";s:3:"片";s:3:"⽛";s:3:"牙";s:3:"⽜";s:3:"牛";s:3:"⽝";s:3:"犬";s:3:"⽞";s:3:"玄";s:3:"⽟";s:3:"玉";s:3:"⽠";s:3:"瓜";s:3:"⽡";s:3:"瓦";s:3:"⽢";s:3:"甘";s:3:"⽣";s:3:"生";s:3:"⽤";s:3:"用";s:3:"⽥";s:3:"田";s:3:"⽦";s:3:"疋";s:3:"⽧";s:3:"疒";s:3:"⽨";s:3:"癶";s:3:"⽩";s:3:"白";s:3:"⽪";s:3:"皮";s:3:"⽫";s:3:"皿";s:3:"⽬";s:3:"目";s:3:"⽭";s:3:"矛";s:3:"⽮";s:3:"矢";s:3:"⽯";s:3:"石";s:3:"⽰";s:3:"示";s:3:"⽱";s:3:"禸";s:3:"⽲";s:3:"禾";s:3:"⽳";s:3:"穴";s:3:"⽴";s:3:"立";s:3:"⽵";s:3:"竹";s:3:"⽶";s:3:"米";s:3:"⽷";s:3:"糸";s:3:"⽸";s:3:"缶";s:3:"⽹";s:3:"网";s:3:"⽺";s:3:"羊";s:3:"⽻";s:3:"羽";s:3:"⽼";s:3:"老";s:3:"⽽";s:3:"而";s:3:"⽾";s:3:"耒";s:3:"⽿";s:3:"耳";s:3:"⾀";s:3:"聿";s:3:"⾁";s:3:"肉";s:3:"⾂";s:3:"臣";s:3:"⾃";s:3:"自";s:3:"⾄";s:3:"至";s:3:"⾅";s:3:"臼";s:3:"⾆";s:3:"舌";s:3:"⾇";s:3:"舛";s:3:"⾈";s:3:"舟";s:3:"⾉";s:3:"艮";s:3:"⾊";s:3:"色";s:3:"⾋";s:3:"艸";s:3:"⾌";s:3:"虍";s:3:"⾍";s:3:"虫";s:3:"⾎";s:3:"血";s:3:"⾏";s:3:"行";s:3:"⾐";s:3:"衣";s:3:"⾑";s:3:"襾";s:3:"⾒";s:3:"見";s:3:"⾓";s:3:"角";s:3:"⾔";s:3:"言";s:3:"⾕";s:3:"谷";s:3:"⾖";s:3:"豆";s:3:"⾗";s:3:"豕";s:3:"⾘";s:3:"豸";s:3:"⾙";s:3:"貝";s:3:"⾚";s:3:"赤";s:3:"⾛";s:3:"走";s:3:"⾜";s:3:"足";s:3:"⾝";s:3:"身";s:3:"⾞";s:3:"車";s:3:"⾟";s:3:"辛";s:3:"⾠";s:3:"辰";s:3:"⾡";s:3:"辵";s:3:"⾢";s:3:"邑";s:3:"⾣";s:3:"酉";s:3:"⾤";s:3:"釆";s:3:"⾥";s:3:"里";s:3:"⾦";s:3:"金";s:3:"⾧";s:3:"長";s:3:"⾨";s:3:"門";s:3:"⾩";s:3:"阜";s:3:"⾪";s:3:"隶";s:3:"⾫";s:3:"隹";s:3:"⾬";s:3:"雨";s:3:"⾭";s:3:"靑";s:3:"⾮";s:3:"非";s:3:"⾯";s:3:"面";s:3:"⾰";s:3:"革";s:3:"⾱";s:3:"韋";s:3:"⾲";s:3:"韭";s:3:"⾳";s:3:"音";s:3:"⾴";s:3:"頁";s:3:"⾵";s:3:"風";s:3:"⾶";s:3:"飛";s:3:"⾷";s:3:"食";s:3:"⾸";s:3:"首";s:3:"⾹";s:3:"香";s:3:"⾺";s:3:"馬";s:3:"⾻";s:3:"骨";s:3:"⾼";s:3:"高";s:3:"⾽";s:3:"髟";s:3:"⾾";s:3:"鬥";s:3:"⾿";s:3:"鬯";s:3:"⿀";s:3:"鬲";s:3:"⿁";s:3:"鬼";s:3:"⿂";s:3:"魚";s:3:"⿃";s:3:"鳥";s:3:"⿄";s:3:"鹵";s:3:"⿅";s:3:"鹿";s:3:"⿆";s:3:"麥";s:3:"⿇";s:3:"麻";s:3:"⿈";s:3:"黃";s:3:"⿉";s:3:"黍";s:3:"⿊";s:3:"黑";s:3:"⿋";s:3:"黹";s:3:"⿌";s:3:"黽";s:3:"⿍";s:3:"鼎";s:3:"⿎";s:3:"鼓";s:3:"⿏";s:3:"鼠";s:3:"⿐";s:3:"鼻";s:3:"⿑";s:3:"齊";s:3:"⿒";s:3:"齒";s:3:"⿓";s:3:"龍";s:3:"⿔";s:3:"龜";s:3:"⿕";s:3:"龠";s:3:" ";s:1:" ";s:3:"〶";s:3:"〒";s:3:"〸";s:3:"十";s:3:"〹";s:3:"卄";s:3:"〺";s:3:"卅";s:3:"が";s:6:"が";s:3:"ぎ";s:6:"ぎ";s:3:"ぐ";s:6:"ぐ";s:3:"げ";s:6:"げ";s:3:"ご";s:6:"ご";s:3:"ざ";s:6:"ざ";s:3:"じ";s:6:"じ";s:3:"ず";s:6:"ず";s:3:"ぜ";s:6:"ぜ";s:3:"ぞ";s:6:"ぞ";s:3:"だ";s:6:"だ";s:3:"ぢ";s:6:"ぢ";s:3:"づ";s:6:"づ";s:3:"で";s:6:"で";s:3:"ど";s:6:"ど";s:3:"ば";s:6:"ば";s:3:"ぱ";s:6:"ぱ";s:3:"び";s:6:"び";s:3:"ぴ";s:6:"ぴ";s:3:"ぶ";s:6:"ぶ";s:3:"ぷ";s:6:"ぷ";s:3:"べ";s:6:"べ";s:3:"ぺ";s:6:"ぺ";s:3:"ぼ";s:6:"ぼ";s:3:"ぽ";s:6:"ぽ";s:3:"ゔ";s:6:"ゔ";s:3:"゛";s:4:" ゙";s:3:"゜";s:4:" ゚";s:3:"ゞ";s:6:"ゞ";s:3:"ゟ";s:6:"より";s:3:"ガ";s:6:"ガ";s:3:"ギ";s:6:"ギ";s:3:"グ";s:6:"グ";s:3:"ゲ";s:6:"ゲ";s:3:"ゴ";s:6:"ゴ";s:3:"ザ";s:6:"ザ";s:3:"ジ";s:6:"ジ";s:3:"ズ";s:6:"ズ";s:3:"ゼ";s:6:"ゼ";s:3:"ゾ";s:6:"ゾ";s:3:"ダ";s:6:"ダ";s:3:"ヂ";s:6:"ヂ";s:3:"ヅ";s:6:"ヅ";s:3:"デ";s:6:"デ";s:3:"ド";s:6:"ド";s:3:"バ";s:6:"バ";s:3:"パ";s:6:"パ";s:3:"ビ";s:6:"ビ";s:3:"ピ";s:6:"ピ";s:3:"ブ";s:6:"ブ";s:3:"プ";s:6:"プ";s:3:"ベ";s:6:"ベ";s:3:"ペ";s:6:"ペ";s:3:"ボ";s:6:"ボ";s:3:"ポ";s:6:"ポ";s:3:"ヴ";s:6:"ヴ";s:3:"ヷ";s:6:"ヷ";s:3:"ヸ";s:6:"ヸ";s:3:"ヹ";s:6:"ヹ";s:3:"ヺ";s:6:"ヺ";s:3:"ヾ";s:6:"ヾ";s:3:"ヿ";s:6:"コト";s:3:"ㄱ";s:3:"ᄀ";s:3:"ㄲ";s:3:"ᄁ";s:3:"ㄳ";s:3:"ᆪ";s:3:"ㄴ";s:3:"ᄂ";s:3:"ㄵ";s:3:"ᆬ";s:3:"ㄶ";s:3:"ᆭ";s:3:"ㄷ";s:3:"ᄃ";s:3:"ㄸ";s:3:"ᄄ";s:3:"ㄹ";s:3:"ᄅ";s:3:"ㄺ";s:3:"ᆰ";s:3:"ㄻ";s:3:"ᆱ";s:3:"ㄼ";s:3:"ᆲ";s:3:"ㄽ";s:3:"ᆳ";s:3:"ㄾ";s:3:"ᆴ";s:3:"ㄿ";s:3:"ᆵ";s:3:"ㅀ";s:3:"ᄚ";s:3:"ㅁ";s:3:"ᄆ";s:3:"ㅂ";s:3:"ᄇ";s:3:"ㅃ";s:3:"ᄈ";s:3:"ㅄ";s:3:"ᄡ";s:3:"ㅅ";s:3:"ᄉ";s:3:"ㅆ";s:3:"ᄊ";s:3:"ㅇ";s:3:"ᄋ";s:3:"ㅈ";s:3:"ᄌ";s:3:"ㅉ";s:3:"ᄍ";s:3:"ㅊ";s:3:"ᄎ";s:3:"ㅋ";s:3:"ᄏ";s:3:"ㅌ";s:3:"ᄐ";s:3:"ㅍ";s:3:"ᄑ";s:3:"ㅎ";s:3:"ᄒ";s:3:"ㅏ";s:3:"ᅡ";s:3:"ㅐ";s:3:"ᅢ";s:3:"ㅑ";s:3:"ᅣ";s:3:"ㅒ";s:3:"ᅤ";s:3:"ㅓ";s:3:"ᅥ";s:3:"ㅔ";s:3:"ᅦ";s:3:"ㅕ";s:3:"ᅧ";s:3:"ㅖ";s:3:"ᅨ";s:3:"ㅗ";s:3:"ᅩ";s:3:"ㅘ";s:3:"ᅪ";s:3:"ㅙ";s:3:"ᅫ";s:3:"ㅚ";s:3:"ᅬ";s:3:"ㅛ";s:3:"ᅭ";s:3:"ㅜ";s:3:"ᅮ";s:3:"ㅝ";s:3:"ᅯ";s:3:"ㅞ";s:3:"ᅰ";s:3:"ㅟ";s:3:"ᅱ";s:3:"ㅠ";s:3:"ᅲ";s:3:"ㅡ";s:3:"ᅳ";s:3:"ㅢ";s:3:"ᅴ";s:3:"ㅣ";s:3:"ᅵ";s:3:"ㅤ";s:3:"ᅠ";s:3:"ㅥ";s:3:"ᄔ";s:3:"ㅦ";s:3:"ᄕ";s:3:"ㅧ";s:3:"ᇇ";s:3:"ㅨ";s:3:"ᇈ";s:3:"ㅩ";s:3:"ᇌ";s:3:"ㅪ";s:3:"ᇎ";s:3:"ㅫ";s:3:"ᇓ";s:3:"ㅬ";s:3:"ᇗ";s:3:"ㅭ";s:3:"ᇙ";s:3:"ㅮ";s:3:"ᄜ";s:3:"ㅯ";s:3:"ᇝ";s:3:"ㅰ";s:3:"ᇟ";s:3:"ㅱ";s:3:"ᄝ";s:3:"ㅲ";s:3:"ᄞ";s:3:"ㅳ";s:3:"ᄠ";s:3:"ㅴ";s:3:"ᄢ";s:3:"ㅵ";s:3:"ᄣ";s:3:"ㅶ";s:3:"ᄧ";s:3:"ㅷ";s:3:"ᄩ";s:3:"ㅸ";s:3:"ᄫ";s:3:"ㅹ";s:3:"ᄬ";s:3:"ㅺ";s:3:"ᄭ";s:3:"ㅻ";s:3:"ᄮ";s:3:"ㅼ";s:3:"ᄯ";s:3:"ㅽ";s:3:"ᄲ";s:3:"ㅾ";s:3:"ᄶ";s:3:"ㅿ";s:3:"ᅀ";s:3:"ㆀ";s:3:"ᅇ";s:3:"ㆁ";s:3:"ᅌ";s:3:"ㆂ";s:3:"ᇱ";s:3:"ㆃ";s:3:"ᇲ";s:3:"ㆄ";s:3:"ᅗ";s:3:"ㆅ";s:3:"ᅘ";s:3:"ㆆ";s:3:"ᅙ";s:3:"ㆇ";s:3:"ᆄ";s:3:"ㆈ";s:3:"ᆅ";s:3:"ㆉ";s:3:"ᆈ";s:3:"ㆊ";s:3:"ᆑ";s:3:"ㆋ";s:3:"ᆒ";s:3:"ㆌ";s:3:"ᆔ";s:3:"ㆍ";s:3:"ᆞ";s:3:"ㆎ";s:3:"ᆡ";s:3:"㆒";s:3:"一";s:3:"㆓";s:3:"二";s:3:"㆔";s:3:"三";s:3:"㆕";s:3:"四";s:3:"㆖";s:3:"上";s:3:"㆗";s:3:"中";s:3:"㆘";s:3:"下";s:3:"㆙";s:3:"甲";s:3:"㆚";s:3:"乙";s:3:"㆛";s:3:"丙";s:3:"㆜";s:3:"丁";s:3:"㆝";s:3:"天";s:3:"㆞";s:3:"地";s:3:"㆟";s:3:"人";s:3:"㈀";s:5:"(ᄀ)";s:3:"㈁";s:5:"(ᄂ)";s:3:"㈂";s:5:"(ᄃ)";s:3:"㈃";s:5:"(ᄅ)";s:3:"㈄";s:5:"(ᄆ)";s:3:"㈅";s:5:"(ᄇ)";s:3:"㈆";s:5:"(ᄉ)";s:3:"㈇";s:5:"(ᄋ)";s:3:"㈈";s:5:"(ᄌ)";s:3:"㈉";s:5:"(ᄎ)";s:3:"㈊";s:5:"(ᄏ)";s:3:"㈋";s:5:"(ᄐ)";s:3:"㈌";s:5:"(ᄑ)";s:3:"㈍";s:5:"(ᄒ)";s:3:"㈎";s:8:"(가)";s:3:"㈏";s:8:"(나)";s:3:"㈐";s:8:"(다)";s:3:"㈑";s:8:"(라)";s:3:"㈒";s:8:"(마)";s:3:"㈓";s:8:"(바)";s:3:"㈔";s:8:"(사)";s:3:"㈕";s:8:"(아)";s:3:"㈖";s:8:"(자)";s:3:"㈗";s:8:"(차)";s:3:"㈘";s:8:"(카)";s:3:"㈙";s:8:"(타)";s:3:"㈚";s:8:"(파)";s:3:"㈛";s:8:"(하)";s:3:"㈜";s:8:"(주)";s:3:"㈝";s:17:"(오전)";s:3:"㈞";s:14:"(오후)";s:3:"㈠";s:5:"(一)";s:3:"㈡";s:5:"(二)";s:3:"㈢";s:5:"(三)";s:3:"㈣";s:5:"(四)";s:3:"㈤";s:5:"(五)";s:3:"㈥";s:5:"(六)";s:3:"㈦";s:5:"(七)";s:3:"㈧";s:5:"(八)";s:3:"㈨";s:5:"(九)";s:3:"㈩";s:5:"(十)";s:3:"㈪";s:5:"(月)";s:3:"㈫";s:5:"(火)";s:3:"㈬";s:5:"(水)";s:3:"㈭";s:5:"(木)";s:3:"㈮";s:5:"(金)";s:3:"㈯";s:5:"(土)";s:3:"㈰";s:5:"(日)";s:3:"㈱";s:5:"(株)";s:3:"㈲";s:5:"(有)";s:3:"㈳";s:5:"(社)";s:3:"㈴";s:5:"(名)";s:3:"㈵";s:5:"(特)";s:3:"㈶";s:5:"(財)";s:3:"㈷";s:5:"(祝)";s:3:"㈸";s:5:"(労)";s:3:"㈹";s:5:"(代)";s:3:"㈺";s:5:"(呼)";s:3:"㈻";s:5:"(学)";s:3:"㈼";s:5:"(監)";s:3:"㈽";s:5:"(企)";s:3:"㈾";s:5:"(資)";s:3:"㈿";s:5:"(協)";s:3:"㉀";s:5:"(祭)";s:3:"㉁";s:5:"(休)";s:3:"㉂";s:5:"(自)";s:3:"㉃";s:5:"(至)";s:3:"㉄";s:3:"問";s:3:"㉅";s:3:"幼";s:3:"㉆";s:3:"文";s:3:"㉇";s:3:"箏";s:3:"㉐";s:3:"PTE";s:3:"㉑";s:2:"21";s:3:"㉒";s:2:"22";s:3:"㉓";s:2:"23";s:3:"㉔";s:2:"24";s:3:"㉕";s:2:"25";s:3:"㉖";s:2:"26";s:3:"㉗";s:2:"27";s:3:"㉘";s:2:"28";s:3:"㉙";s:2:"29";s:3:"㉚";s:2:"30";s:3:"㉛";s:2:"31";s:3:"㉜";s:2:"32";s:3:"㉝";s:2:"33";s:3:"㉞";s:2:"34";s:3:"㉟";s:2:"35";s:3:"㉠";s:3:"ᄀ";s:3:"㉡";s:3:"ᄂ";s:3:"㉢";s:3:"ᄃ";s:3:"㉣";s:3:"ᄅ";s:3:"㉤";s:3:"ᄆ";s:3:"㉥";s:3:"ᄇ";s:3:"㉦";s:3:"ᄉ";s:3:"㉧";s:3:"ᄋ";s:3:"㉨";s:3:"ᄌ";s:3:"㉩";s:3:"ᄎ";s:3:"㉪";s:3:"ᄏ";s:3:"㉫";s:3:"ᄐ";s:3:"㉬";s:3:"ᄑ";s:3:"㉭";s:3:"ᄒ";s:3:"㉮";s:6:"가";s:3:"㉯";s:6:"나";s:3:"㉰";s:6:"다";s:3:"㉱";s:6:"라";s:3:"㉲";s:6:"마";s:3:"㉳";s:6:"바";s:3:"㉴";s:6:"사";s:3:"㉵";s:6:"아";s:3:"㉶";s:6:"자";s:3:"㉷";s:6:"차";s:3:"㉸";s:6:"카";s:3:"㉹";s:6:"타";s:3:"㉺";s:6:"파";s:3:"㉻";s:6:"하";s:3:"㉼";s:15:"참고";s:3:"㉽";s:12:"주의";s:3:"㉾";s:6:"우";s:3:"㊀";s:3:"一";s:3:"㊁";s:3:"二";s:3:"㊂";s:3:"三";s:3:"㊃";s:3:"四";s:3:"㊄";s:3:"五";s:3:"㊅";s:3:"六";s:3:"㊆";s:3:"七";s:3:"㊇";s:3:"八";s:3:"㊈";s:3:"九";s:3:"㊉";s:3:"十";s:3:"㊊";s:3:"月";s:3:"㊋";s:3:"火";s:3:"㊌";s:3:"水";s:3:"㊍";s:3:"木";s:3:"㊎";s:3:"金";s:3:"㊏";s:3:"土";s:3:"㊐";s:3:"日";s:3:"㊑";s:3:"株";s:3:"㊒";s:3:"有";s:3:"㊓";s:3:"社";s:3:"㊔";s:3:"名";s:3:"㊕";s:3:"特";s:3:"㊖";s:3:"財";s:3:"㊗";s:3:"祝";s:3:"㊘";s:3:"労";s:3:"㊙";s:3:"秘";s:3:"㊚";s:3:"男";s:3:"㊛";s:3:"女";s:3:"㊜";s:3:"適";s:3:"㊝";s:3:"優";s:3:"㊞";s:3:"印";s:3:"㊟";s:3:"注";s:3:"㊠";s:3:"項";s:3:"㊡";s:3:"休";s:3:"㊢";s:3:"写";s:3:"㊣";s:3:"正";s:3:"㊤";s:3:"上";s:3:"㊥";s:3:"中";s:3:"㊦";s:3:"下";s:3:"㊧";s:3:"左";s:3:"㊨";s:3:"右";s:3:"㊩";s:3:"医";s:3:"㊪";s:3:"宗";s:3:"㊫";s:3:"学";s:3:"㊬";s:3:"監";s:3:"㊭";s:3:"企";s:3:"㊮";s:3:"資";s:3:"㊯";s:3:"協";s:3:"㊰";s:3:"夜";s:3:"㊱";s:2:"36";s:3:"㊲";s:2:"37";s:3:"㊳";s:2:"38";s:3:"㊴";s:2:"39";s:3:"㊵";s:2:"40";s:3:"㊶";s:2:"41";s:3:"㊷";s:2:"42";s:3:"㊸";s:2:"43";s:3:"㊹";s:2:"44";s:3:"㊺";s:2:"45";s:3:"㊻";s:2:"46";s:3:"㊼";s:2:"47";s:3:"㊽";s:2:"48";s:3:"㊾";s:2:"49";s:3:"㊿";s:2:"50";s:3:"㋀";s:4:"1月";s:3:"㋁";s:4:"2月";s:3:"㋂";s:4:"3月";s:3:"㋃";s:4:"4月";s:3:"㋄";s:4:"5月";s:3:"㋅";s:4:"6月";s:3:"㋆";s:4:"7月";s:3:"㋇";s:4:"8月";s:3:"㋈";s:4:"9月";s:3:"㋉";s:5:"10月";s:3:"㋊";s:5:"11月";s:3:"㋋";s:5:"12月";s:3:"㋌";s:2:"Hg";s:3:"㋍";s:3:"erg";s:3:"㋎";s:2:"eV";s:3:"㋏";s:3:"LTD";s:3:"㋐";s:3:"ア";s:3:"㋑";s:3:"イ";s:3:"㋒";s:3:"ウ";s:3:"㋓";s:3:"エ";s:3:"㋔";s:3:"オ";s:3:"㋕";s:3:"カ";s:3:"㋖";s:3:"キ";s:3:"㋗";s:3:"ク";s:3:"㋘";s:3:"ケ";s:3:"㋙";s:3:"コ";s:3:"㋚";s:3:"サ";s:3:"㋛";s:3:"シ";s:3:"㋜";s:3:"ス";s:3:"㋝";s:3:"セ";s:3:"㋞";s:3:"ソ";s:3:"㋟";s:3:"タ";s:3:"㋠";s:3:"チ";s:3:"㋡";s:3:"ツ";s:3:"㋢";s:3:"テ";s:3:"㋣";s:3:"ト";s:3:"㋤";s:3:"ナ";s:3:"㋥";s:3:"ニ";s:3:"㋦";s:3:"ヌ";s:3:"㋧";s:3:"ネ";s:3:"㋨";s:3:"ノ";s:3:"㋩";s:3:"ハ";s:3:"㋪";s:3:"ヒ";s:3:"㋫";s:3:"フ";s:3:"㋬";s:3:"ヘ";s:3:"㋭";s:3:"ホ";s:3:"㋮";s:3:"マ";s:3:"㋯";s:3:"ミ";s:3:"㋰";s:3:"ム";s:3:"㋱";s:3:"メ";s:3:"㋲";s:3:"モ";s:3:"㋳";s:3:"ヤ";s:3:"㋴";s:3:"ユ";s:3:"㋵";s:3:"ヨ";s:3:"㋶";s:3:"ラ";s:3:"㋷";s:3:"リ";s:3:"㋸";s:3:"ル";s:3:"㋹";s:3:"レ";s:3:"㋺";s:3:"ロ";s:3:"㋻";s:3:"ワ";s:3:"㋼";s:3:"ヰ";s:3:"㋽";s:3:"ヱ";s:3:"㋾";s:3:"ヲ";s:3:"㌀";s:15:"アパート";s:3:"㌁";s:12:"アルファ";s:3:"㌂";s:15:"アンペア";s:3:"㌃";s:9:"アール";s:3:"㌄";s:15:"イニング";s:3:"㌅";s:9:"インチ";s:3:"㌆";s:9:"ウォン";s:3:"㌇";s:18:"エスクード";s:3:"㌈";s:12:"エーカー";s:3:"㌉";s:9:"オンス";s:3:"㌊";s:9:"オーム";s:3:"㌋";s:9:"カイリ";s:3:"㌌";s:12:"カラット";s:3:"㌍";s:12:"カロリー";s:3:"㌎";s:12:"ガロン";s:3:"㌏";s:12:"ガンマ";s:3:"㌐";s:12:"ギガ";s:3:"㌑";s:12:"ギニー";s:3:"㌒";s:12:"キュリー";s:3:"㌓";s:18:"ギルダー";s:3:"㌔";s:6:"キロ";s:3:"㌕";s:18:"キログラム";s:3:"㌖";s:18:"キロメートル";s:3:"㌗";s:15:"キロワット";s:3:"㌘";s:12:"グラム";s:3:"㌙";s:18:"グラムトン";s:3:"㌚";s:18:"クルゼイロ";s:3:"㌛";s:12:"クローネ";s:3:"㌜";s:9:"ケース";s:3:"㌝";s:9:"コルナ";s:3:"㌞";s:12:"コーポ";s:3:"㌟";s:12:"サイクル";s:3:"㌠";s:15:"サンチーム";s:3:"㌡";s:15:"シリング";s:3:"㌢";s:9:"センチ";s:3:"㌣";s:9:"セント";s:3:"㌤";s:12:"ダース";s:3:"㌥";s:9:"デシ";s:3:"㌦";s:9:"ドル";s:3:"㌧";s:6:"トン";s:3:"㌨";s:6:"ナノ";s:3:"㌩";s:9:"ノット";s:3:"㌪";s:9:"ハイツ";s:3:"㌫";s:18:"パーセント";s:3:"㌬";s:12:"パーツ";s:3:"㌭";s:15:"バーレル";s:3:"㌮";s:18:"ピアストル";s:3:"㌯";s:12:"ピクル";s:3:"㌰";s:9:"ピコ";s:3:"㌱";s:9:"ビル";s:3:"㌲";s:18:"ファラッド";s:3:"㌳";s:12:"フィート";s:3:"㌴";s:18:"ブッシェル";s:3:"㌵";s:9:"フラン";s:3:"㌶";s:15:"ヘクタール";s:3:"㌷";s:9:"ペソ";s:3:"㌸";s:12:"ペニヒ";s:3:"㌹";s:9:"ヘルツ";s:3:"㌺";s:12:"ペンス";s:3:"㌻";s:15:"ページ";s:3:"㌼";s:12:"ベータ";s:3:"㌽";s:15:"ポイント";s:3:"㌾";s:12:"ボルト";s:3:"㌿";s:6:"ホン";s:3:"㍀";s:15:"ポンド";s:3:"㍁";s:9:"ホール";s:3:"㍂";s:9:"ホーン";s:3:"㍃";s:12:"マイクロ";s:3:"㍄";s:9:"マイル";s:3:"㍅";s:9:"マッハ";s:3:"㍆";s:9:"マルク";s:3:"㍇";s:15:"マンション";s:3:"㍈";s:12:"ミクロン";s:3:"㍉";s:6:"ミリ";s:3:"㍊";s:18:"ミリバール";s:3:"㍋";s:9:"メガ";s:3:"㍌";s:15:"メガトン";s:3:"㍍";s:12:"メートル";s:3:"㍎";s:12:"ヤード";s:3:"㍏";s:9:"ヤール";s:3:"㍐";s:9:"ユアン";s:3:"㍑";s:12:"リットル";s:3:"㍒";s:6:"リラ";s:3:"㍓";s:12:"ルピー";s:3:"㍔";s:15:"ルーブル";s:3:"㍕";s:6:"レム";s:3:"㍖";s:18:"レントゲン";s:3:"㍗";s:9:"ワット";s:3:"㍘";s:4:"0点";s:3:"㍙";s:4:"1点";s:3:"㍚";s:4:"2点";s:3:"㍛";s:4:"3点";s:3:"㍜";s:4:"4点";s:3:"㍝";s:4:"5点";s:3:"㍞";s:4:"6点";s:3:"㍟";s:4:"7点";s:3:"㍠";s:4:"8点";s:3:"㍡";s:4:"9点";s:3:"㍢";s:5:"10点";s:3:"㍣";s:5:"11点";s:3:"㍤";s:5:"12点";s:3:"㍥";s:5:"13点";s:3:"㍦";s:5:"14点";s:3:"㍧";s:5:"15点";s:3:"㍨";s:5:"16点";s:3:"㍩";s:5:"17点";s:3:"㍪";s:5:"18点";s:3:"㍫";s:5:"19点";s:3:"㍬";s:5:"20点";s:3:"㍭";s:5:"21点";s:3:"㍮";s:5:"22点";s:3:"㍯";s:5:"23点";s:3:"㍰";s:5:"24点";s:3:"㍱";s:3:"hPa";s:3:"㍲";s:2:"da";s:3:"㍳";s:2:"AU";s:3:"㍴";s:3:"bar";s:3:"㍵";s:2:"oV";s:3:"㍶";s:2:"pc";s:3:"㍷";s:2:"dm";s:3:"㍸";s:3:"dm2";s:3:"㍹";s:3:"dm3";s:3:"㍺";s:2:"IU";s:3:"㍻";s:6:"平成";s:3:"㍼";s:6:"昭和";s:3:"㍽";s:6:"大正";s:3:"㍾";s:6:"明治";s:3:"㍿";s:12:"株式会社";s:3:"㎀";s:2:"pA";s:3:"㎁";s:2:"nA";s:3:"㎂";s:3:"μA";s:3:"㎃";s:2:"mA";s:3:"㎄";s:2:"kA";s:3:"㎅";s:2:"KB";s:3:"㎆";s:2:"MB";s:3:"㎇";s:2:"GB";s:3:"㎈";s:3:"cal";s:3:"㎉";s:4:"kcal";s:3:"㎊";s:2:"pF";s:3:"㎋";s:2:"nF";s:3:"㎌";s:3:"μF";s:3:"㎍";s:3:"μg";s:3:"㎎";s:2:"mg";s:3:"㎏";s:2:"kg";s:3:"㎐";s:2:"Hz";s:3:"㎑";s:3:"kHz";s:3:"㎒";s:3:"MHz";s:3:"㎓";s:3:"GHz";s:3:"㎔";s:3:"THz";s:3:"㎕";s:3:"μl";s:3:"㎖";s:2:"ml";s:3:"㎗";s:2:"dl";s:3:"㎘";s:2:"kl";s:3:"㎙";s:2:"fm";s:3:"㎚";s:2:"nm";s:3:"㎛";s:3:"μm";s:3:"㎜";s:2:"mm";s:3:"㎝";s:2:"cm";s:3:"㎞";s:2:"km";s:3:"㎟";s:3:"mm2";s:3:"㎠";s:3:"cm2";s:3:"㎡";s:2:"m2";s:3:"㎢";s:3:"km2";s:3:"㎣";s:3:"mm3";s:3:"㎤";s:3:"cm3";s:3:"㎥";s:2:"m3";s:3:"㎦";s:3:"km3";s:3:"㎧";s:5:"m∕s";s:3:"㎨";s:6:"m∕s2";s:3:"㎩";s:2:"Pa";s:3:"㎪";s:3:"kPa";s:3:"㎫";s:3:"MPa";s:3:"㎬";s:3:"GPa";s:3:"㎭";s:3:"rad";s:3:"㎮";s:7:"rad∕s";s:3:"㎯";s:8:"rad∕s2";s:3:"㎰";s:2:"ps";s:3:"㎱";s:2:"ns";s:3:"㎲";s:3:"μs";s:3:"㎳";s:2:"ms";s:3:"㎴";s:2:"pV";s:3:"㎵";s:2:"nV";s:3:"㎶";s:3:"μV";s:3:"㎷";s:2:"mV";s:3:"㎸";s:2:"kV";s:3:"㎹";s:2:"MV";s:3:"㎺";s:2:"pW";s:3:"㎻";s:2:"nW";s:3:"㎼";s:3:"μW";s:3:"㎽";s:2:"mW";s:3:"㎾";s:2:"kW";s:3:"㎿";s:2:"MW";s:3:"㏀";s:3:"kΩ";s:3:"㏁";s:3:"MΩ";s:3:"㏂";s:4:"a.m.";s:3:"㏃";s:2:"Bq";s:3:"㏄";s:2:"cc";s:3:"㏅";s:2:"cd";s:3:"㏆";s:6:"C∕kg";s:3:"㏇";s:3:"Co.";s:3:"㏈";s:2:"dB";s:3:"㏉";s:2:"Gy";s:3:"㏊";s:2:"ha";s:3:"㏋";s:2:"HP";s:3:"㏌";s:2:"in";s:3:"㏍";s:2:"KK";s:3:"㏎";s:2:"KM";s:3:"㏏";s:2:"kt";s:3:"㏐";s:2:"lm";s:3:"㏑";s:2:"ln";s:3:"㏒";s:3:"log";s:3:"㏓";s:2:"lx";s:3:"㏔";s:2:"mb";s:3:"㏕";s:3:"mil";s:3:"㏖";s:3:"mol";s:3:"㏗";s:2:"PH";s:3:"㏘";s:4:"p.m.";s:3:"㏙";s:3:"PPM";s:3:"㏚";s:2:"PR";s:3:"㏛";s:2:"sr";s:3:"㏜";s:2:"Sv";s:3:"㏝";s:2:"Wb";s:3:"㏞";s:5:"V∕m";s:3:"㏟";s:5:"A∕m";s:3:"㏠";s:4:"1日";s:3:"㏡";s:4:"2日";s:3:"㏢";s:4:"3日";s:3:"㏣";s:4:"4日";s:3:"㏤";s:4:"5日";s:3:"㏥";s:4:"6日";s:3:"㏦";s:4:"7日";s:3:"㏧";s:4:"8日";s:3:"㏨";s:4:"9日";s:3:"㏩";s:5:"10日";s:3:"㏪";s:5:"11日";s:3:"㏫";s:5:"12日";s:3:"㏬";s:5:"13日";s:3:"㏭";s:5:"14日";s:3:"㏮";s:5:"15日";s:3:"㏯";s:5:"16日";s:3:"㏰";s:5:"17日";s:3:"㏱";s:5:"18日";s:3:"㏲";s:5:"19日";s:3:"㏳";s:5:"20日";s:3:"㏴";s:5:"21日";s:3:"㏵";s:5:"22日";s:3:"㏶";s:5:"23日";s:3:"㏷";s:5:"24日";s:3:"㏸";s:5:"25日";s:3:"㏹";s:5:"26日";s:3:"㏺";s:5:"27日";s:3:"㏻";s:5:"28日";s:3:"㏼";s:5:"29日";s:3:"㏽";s:5:"30日";s:3:"㏾";s:5:"31日";s:3:"㏿";s:3:"gal";s:3:"ꝰ";s:3:"ꝯ";s:3:"豈";s:3:"豈";s:3:"更";s:3:"更";s:3:"車";s:3:"車";s:3:"賈";s:3:"賈";s:3:"滑";s:3:"滑";s:3:"串";s:3:"串";s:3:"句";s:3:"句";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"龜";s:3:"契";s:3:"契";s:3:"金";s:3:"金";s:3:"喇";s:3:"喇";s:3:"奈";s:3:"奈";s:3:"懶";s:3:"懶";s:3:"癩";s:3:"癩";s:3:"羅";s:3:"羅";s:3:"蘿";s:3:"蘿";s:3:"螺";s:3:"螺";s:3:"裸";s:3:"裸";s:3:"邏";s:3:"邏";s:3:"樂";s:3:"樂";s:3:"洛";s:3:"洛";s:3:"烙";s:3:"烙";s:3:"珞";s:3:"珞";s:3:"落";s:3:"落";s:3:"酪";s:3:"酪";s:3:"駱";s:3:"駱";s:3:"亂";s:3:"亂";s:3:"卵";s:3:"卵";s:3:"欄";s:3:"欄";s:3:"爛";s:3:"爛";s:3:"蘭";s:3:"蘭";s:3:"鸞";s:3:"鸞";s:3:"嵐";s:3:"嵐";s:3:"濫";s:3:"濫";s:3:"藍";s:3:"藍";s:3:"襤";s:3:"襤";s:3:"拉";s:3:"拉";s:3:"臘";s:3:"臘";s:3:"蠟";s:3:"蠟";s:3:"廊";s:3:"廊";s:3:"朗";s:3:"朗";s:3:"浪";s:3:"浪";s:3:"狼";s:3:"狼";s:3:"郎";s:3:"郎";s:3:"來";s:3:"來";s:3:"冷";s:3:"冷";s:3:"勞";s:3:"勞";s:3:"擄";s:3:"擄";s:3:"櫓";s:3:"櫓";s:3:"爐";s:3:"爐";s:3:"盧";s:3:"盧";s:3:"老";s:3:"老";s:3:"蘆";s:3:"蘆";s:3:"虜";s:3:"虜";s:3:"路";s:3:"路";s:3:"露";s:3:"露";s:3:"魯";s:3:"魯";s:3:"鷺";s:3:"鷺";s:3:"碌";s:3:"碌";s:3:"祿";s:3:"祿";s:3:"綠";s:3:"綠";s:3:"菉";s:3:"菉";s:3:"錄";s:3:"錄";s:3:"鹿";s:3:"鹿";s:3:"論";s:3:"論";s:3:"壟";s:3:"壟";s:3:"弄";s:3:"弄";s:3:"籠";s:3:"籠";s:3:"聾";s:3:"聾";s:3:"牢";s:3:"牢";s:3:"磊";s:3:"磊";s:3:"賂";s:3:"賂";s:3:"雷";s:3:"雷";s:3:"壘";s:3:"壘";s:3:"屢";s:3:"屢";s:3:"樓";s:3:"樓";s:3:"淚";s:3:"淚";s:3:"漏";s:3:"漏";s:3:"累";s:3:"累";s:3:"縷";s:3:"縷";s:3:"陋";s:3:"陋";s:3:"勒";s:3:"勒";s:3:"肋";s:3:"肋";s:3:"凜";s:3:"凜";s:3:"凌";s:3:"凌";s:3:"稜";s:3:"稜";s:3:"綾";s:3:"綾";s:3:"菱";s:3:"菱";s:3:"陵";s:3:"陵";s:3:"讀";s:3:"讀";s:3:"拏";s:3:"拏";s:3:"樂";s:3:"樂";s:3:"諾";s:3:"諾";s:3:"丹";s:3:"丹";s:3:"寧";s:3:"寧";s:3:"怒";s:3:"怒";s:3:"率";s:3:"率";s:3:"異";s:3:"異";s:3:"北";s:3:"北";s:3:"磻";s:3:"磻";s:3:"便";s:3:"便";s:3:"復";s:3:"復";s:3:"不";s:3:"不";s:3:"泌";s:3:"泌";s:3:"數";s:3:"數";s:3:"索";s:3:"索";s:3:"參";s:3:"參";s:3:"塞";s:3:"塞";s:3:"省";s:3:"省";s:3:"葉";s:3:"葉";s:3:"說";s:3:"說";s:3:"殺";s:3:"殺";s:3:"辰";s:3:"辰";s:3:"沈";s:3:"沈";s:3:"拾";s:3:"拾";s:3:"若";s:3:"若";s:3:"掠";s:3:"掠";s:3:"略";s:3:"略";s:3:"亮";s:3:"亮";s:3:"兩";s:3:"兩";s:3:"凉";s:3:"凉";s:3:"梁";s:3:"梁";s:3:"糧";s:3:"糧";s:3:"良";s:3:"良";s:3:"諒";s:3:"諒";s:3:"量";s:3:"量";s:3:"勵";s:3:"勵";s:3:"呂";s:3:"呂";s:3:"女";s:3:"女";s:3:"廬";s:3:"廬";s:3:"旅";s:3:"旅";s:3:"濾";s:3:"濾";s:3:"礪";s:3:"礪";s:3:"閭";s:3:"閭";s:3:"驪";s:3:"驪";s:3:"麗";s:3:"麗";s:3:"黎";s:3:"黎";s:3:"力";s:3:"力";s:3:"曆";s:3:"曆";s:3:"歷";s:3:"歷";s:3:"轢";s:3:"轢";s:3:"年";s:3:"年";s:3:"憐";s:3:"憐";s:3:"戀";s:3:"戀";s:3:"撚";s:3:"撚";s:3:"漣";s:3:"漣";s:3:"煉";s:3:"煉";s:3:"璉";s:3:"璉";s:3:"秊";s:3:"秊";s:3:"練";s:3:"練";s:3:"聯";s:3:"聯";s:3:"輦";s:3:"輦";s:3:"蓮";s:3:"蓮";s:3:"連";s:3:"連";s:3:"鍊";s:3:"鍊";s:3:"列";s:3:"列";s:3:"劣";s:3:"劣";s:3:"咽";s:3:"咽";s:3:"烈";s:3:"烈";s:3:"裂";s:3:"裂";s:3:"說";s:3:"說";s:3:"廉";s:3:"廉";s:3:"念";s:3:"念";s:3:"捻";s:3:"捻";s:3:"殮";s:3:"殮";s:3:"簾";s:3:"簾";s:3:"獵";s:3:"獵";s:3:"令";s:3:"令";s:3:"囹";s:3:"囹";s:3:"寧";s:3:"寧";s:3:"嶺";s:3:"嶺";s:3:"怜";s:3:"怜";s:3:"玲";s:3:"玲";s:3:"瑩";s:3:"瑩";s:3:"羚";s:3:"羚";s:3:"聆";s:3:"聆";s:3:"鈴";s:3:"鈴";s:3:"零";s:3:"零";s:3:"靈";s:3:"靈";s:3:"領";s:3:"領";s:3:"例";s:3:"例";s:3:"禮";s:3:"禮";s:3:"醴";s:3:"醴";s:3:"隸";s:3:"隸";s:3:"惡";s:3:"惡";s:3:"了";s:3:"了";s:3:"僚";s:3:"僚";s:3:"寮";s:3:"寮";s:3:"尿";s:3:"尿";s:3:"料";s:3:"料";s:3:"樂";s:3:"樂";s:3:"燎";s:3:"燎";s:3:"療";s:3:"療";s:3:"蓼";s:3:"蓼";s:3:"遼";s:3:"遼";s:3:"龍";s:3:"龍";s:3:"暈";s:3:"暈";s:3:"阮";s:3:"阮";s:3:"劉";s:3:"劉";s:3:"杻";s:3:"杻";s:3:"柳";s:3:"柳";s:3:"流";s:3:"流";s:3:"溜";s:3:"溜";s:3:"琉";s:3:"琉";s:3:"留";s:3:"留";s:3:"硫";s:3:"硫";s:3:"紐";s:3:"紐";s:3:"類";s:3:"類";s:3:"六";s:3:"六";s:3:"戮";s:3:"戮";s:3:"陸";s:3:"陸";s:3:"倫";s:3:"倫";s:3:"崙";s:3:"崙";s:3:"淪";s:3:"淪";s:3:"輪";s:3:"輪";s:3:"律";s:3:"律";s:3:"慄";s:3:"慄";s:3:"栗";s:3:"栗";s:3:"率";s:3:"率";s:3:"隆";s:3:"隆";s:3:"利";s:3:"利";s:3:"吏";s:3:"吏";s:3:"履";s:3:"履";s:3:"易";s:3:"易";s:3:"李";s:3:"李";s:3:"梨";s:3:"梨";s:3:"泥";s:3:"泥";s:3:"理";s:3:"理";s:3:"痢";s:3:"痢";s:3:"罹";s:3:"罹";s:3:"裏";s:3:"裏";s:3:"裡";s:3:"裡";s:3:"里";s:3:"里";s:3:"離";s:3:"離";s:3:"匿";s:3:"匿";s:3:"溺";s:3:"溺";s:3:"吝";s:3:"吝";s:3:"燐";s:3:"燐";s:3:"璘";s:3:"璘";s:3:"藺";s:3:"藺";s:3:"隣";s:3:"隣";s:3:"鱗";s:3:"鱗";s:3:"麟";s:3:"麟";s:3:"林";s:3:"林";s:3:"淋";s:3:"淋";s:3:"臨";s:3:"臨";s:3:"立";s:3:"立";s:3:"笠";s:3:"笠";s:3:"粒";s:3:"粒";s:3:"狀";s:3:"狀";s:3:"炙";s:3:"炙";s:3:"識";s:3:"識";s:3:"什";s:3:"什";s:3:"茶";s:3:"茶";s:3:"刺";s:3:"刺";s:3:"切";s:3:"切";s:3:"度";s:3:"度";s:3:"拓";s:3:"拓";s:3:"糖";s:3:"糖";s:3:"宅";s:3:"宅";s:3:"洞";s:3:"洞";s:3:"暴";s:3:"暴";s:3:"輻";s:3:"輻";s:3:"行";s:3:"行";s:3:"降";s:3:"降";s:3:"見";s:3:"見";s:3:"廓";s:3:"廓";s:3:"兀";s:3:"兀";s:3:"嗀";s:3:"嗀";s:3:"塚";s:3:"塚";s:3:"晴";s:3:"晴";s:3:"凞";s:3:"凞";s:3:"猪";s:3:"猪";s:3:"益";s:3:"益";s:3:"礼";s:3:"礼";s:3:"神";s:3:"神";s:3:"祥";s:3:"祥";s:3:"福";s:3:"福";s:3:"靖";s:3:"靖";s:3:"精";s:3:"精";s:3:"羽";s:3:"羽";s:3:"蘒";s:3:"蘒";s:3:"諸";s:3:"諸";s:3:"逸";s:3:"逸";s:3:"都";s:3:"都";s:3:"飯";s:3:"飯";s:3:"飼";s:3:"飼";s:3:"館";s:3:"館";s:3:"鶴";s:3:"鶴";s:3:"侮";s:3:"侮";s:3:"僧";s:3:"僧";s:3:"免";s:3:"免";s:3:"勉";s:3:"勉";s:3:"勤";s:3:"勤";s:3:"卑";s:3:"卑";s:3:"喝";s:3:"喝";s:3:"嘆";s:3:"嘆";s:3:"器";s:3:"器";s:3:"塀";s:3:"塀";s:3:"墨";s:3:"墨";s:3:"層";s:3:"層";s:3:"屮";s:3:"屮";s:3:"悔";s:3:"悔";s:3:"慨";s:3:"慨";s:3:"憎";s:3:"憎";s:3:"懲";s:3:"懲";s:3:"敏";s:3:"敏";s:3:"既";s:3:"既";s:3:"暑";s:3:"暑";s:3:"梅";s:3:"梅";s:3:"海";s:3:"海";s:3:"渚";s:3:"渚";s:3:"漢";s:3:"漢";s:3:"煮";s:3:"煮";s:3:"爫";s:3:"爫";s:3:"琢";s:3:"琢";s:3:"碑";s:3:"碑";s:3:"社";s:3:"社";s:3:"祉";s:3:"祉";s:3:"祈";s:3:"祈";s:3:"祐";s:3:"祐";s:3:"祖";s:3:"祖";s:3:"祝";s:3:"祝";s:3:"禍";s:3:"禍";s:3:"禎";s:3:"禎";s:3:"穀";s:3:"穀";s:3:"突";s:3:"突";s:3:"節";s:3:"節";s:3:"練";s:3:"練";s:3:"縉";s:3:"縉";s:3:"繁";s:3:"繁";s:3:"署";s:3:"署";s:3:"者";s:3:"者";s:3:"臭";s:3:"臭";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"艹";s:3:"著";s:3:"著";s:3:"褐";s:3:"褐";s:3:"視";s:3:"視";s:3:"謁";s:3:"謁";s:3:"謹";s:3:"謹";s:3:"賓";s:3:"賓";s:3:"贈";s:3:"贈";s:3:"辶";s:3:"辶";s:3:"逸";s:3:"逸";s:3:"難";s:3:"難";s:3:"響";s:3:"響";s:3:"頻";s:3:"頻";s:3:"恵";s:3:"恵";s:3:"𤋮";s:4:"𤋮";s:3:"舘";s:3:"舘";s:3:"並";s:3:"並";s:3:"况";s:3:"况";s:3:"全";s:3:"全";s:3:"侀";s:3:"侀";s:3:"充";s:3:"充";s:3:"冀";s:3:"冀";s:3:"勇";s:3:"勇";s:3:"勺";s:3:"勺";s:3:"喝";s:3:"喝";s:3:"啕";s:3:"啕";s:3:"喙";s:3:"喙";s:3:"嗢";s:3:"嗢";s:3:"塚";s:3:"塚";s:3:"墳";s:3:"墳";s:3:"奄";s:3:"奄";s:3:"奔";s:3:"奔";s:3:"婢";s:3:"婢";s:3:"嬨";s:3:"嬨";s:3:"廒";s:3:"廒";s:3:"廙";s:3:"廙";s:3:"彩";s:3:"彩";s:3:"徭";s:3:"徭";s:3:"惘";s:3:"惘";s:3:"慎";s:3:"慎";s:3:"愈";s:3:"愈";s:3:"憎";s:3:"憎";s:3:"慠";s:3:"慠";s:3:"懲";s:3:"懲";s:3:"戴";s:3:"戴";s:3:"揄";s:3:"揄";s:3:"搜";s:3:"搜";s:3:"摒";s:3:"摒";s:3:"敖";s:3:"敖";s:3:"晴";s:3:"晴";s:3:"朗";s:3:"朗";s:3:"望";s:3:"望";s:3:"杖";s:3:"杖";s:3:"歹";s:3:"歹";s:3:"殺";s:3:"殺";s:3:"流";s:3:"流";s:3:"滛";s:3:"滛";s:3:"滋";s:3:"滋";s:3:"漢";s:3:"漢";s:3:"瀞";s:3:"瀞";s:3:"煮";s:3:"煮";s:3:"瞧";s:3:"瞧";s:3:"爵";s:3:"爵";s:3:"犯";s:3:"犯";s:3:"猪";s:3:"猪";s:3:"瑱";s:3:"瑱";s:3:"甆";s:3:"甆";s:3:"画";s:3:"画";s:3:"瘝";s:3:"瘝";s:3:"瘟";s:3:"瘟";s:3:"益";s:3:"益";s:3:"盛";s:3:"盛";s:3:"直";s:3:"直";s:3:"睊";s:3:"睊";s:3:"着";s:3:"着";s:3:"磌";s:3:"磌";s:3:"窱";s:3:"窱";s:3:"節";s:3:"節";s:3:"类";s:3:"类";s:3:"絛";s:3:"絛";s:3:"練";s:3:"練";s:3:"缾";s:3:"缾";s:3:"者";s:3:"者";s:3:"荒";s:3:"荒";s:3:"華";s:3:"華";s:3:"蝹";s:3:"蝹";s:3:"襁";s:3:"襁";s:3:"覆";s:3:"覆";s:3:"視";s:3:"視";s:3:"調";s:3:"調";s:3:"諸";s:3:"諸";s:3:"請";s:3:"請";s:3:"謁";s:3:"謁";s:3:"諾";s:3:"諾";s:3:"諭";s:3:"諭";s:3:"謹";s:3:"謹";s:3:"變";s:3:"變";s:3:"贈";s:3:"贈";s:3:"輸";s:3:"輸";s:3:"遲";s:3:"遲";s:3:"醙";s:3:"醙";s:3:"鉶";s:3:"鉶";s:3:"陼";s:3:"陼";s:3:"難";s:3:"難";s:3:"靖";s:3:"靖";s:3:"韛";s:3:"韛";s:3:"響";s:3:"響";s:3:"頋";s:3:"頋";s:3:"頻";s:3:"頻";s:3:"鬒";s:3:"鬒";s:3:"龜";s:3:"龜";s:3:"𢡊";s:4:"𢡊";s:3:"𢡄";s:4:"𢡄";s:3:"𣏕";s:4:"𣏕";s:3:"㮝";s:3:"㮝";s:3:"䀘";s:3:"䀘";s:3:"䀹";s:3:"䀹";s:3:"𥉉";s:4:"𥉉";s:3:"𥳐";s:4:"𥳐";s:3:"𧻓";s:4:"𧻓";s:3:"齃";s:3:"齃";s:3:"龎";s:3:"龎";s:3:"ff";s:2:"ff";s:3:"fi";s:2:"fi";s:3:"fl";s:2:"fl";s:3:"ffi";s:3:"ffi";s:3:"ffl";s:3:"ffl";s:3:"ſt";s:2:"st";s:3:"st";s:2:"st";s:3:"ﬓ";s:4:"մն";s:3:"ﬔ";s:4:"մե";s:3:"ﬕ";s:4:"մի";s:3:"ﬖ";s:4:"վն";s:3:"ﬗ";s:4:"մխ";s:3:"יִ";s:4:"יִ";s:3:"ײַ";s:4:"ײַ";s:3:"ﬠ";s:2:"ע";s:3:"ﬡ";s:2:"א";s:3:"ﬢ";s:2:"ד";s:3:"ﬣ";s:2:"ה";s:3:"ﬤ";s:2:"כ";s:3:"ﬥ";s:2:"ל";s:3:"ﬦ";s:2:"ם";s:3:"ﬧ";s:2:"ר";s:3:"ﬨ";s:2:"ת";s:3:"﬩";s:1:"+";s:3:"שׁ";s:4:"שׁ";s:3:"שׂ";s:4:"שׂ";s:3:"שּׁ";s:6:"שּׁ";s:3:"שּׂ";s:6:"שּׂ";s:3:"אַ";s:4:"אַ";s:3:"אָ";s:4:"אָ";s:3:"אּ";s:4:"אּ";s:3:"בּ";s:4:"בּ";s:3:"גּ";s:4:"גּ";s:3:"דּ";s:4:"דּ";s:3:"הּ";s:4:"הּ";s:3:"וּ";s:4:"וּ";s:3:"זּ";s:4:"זּ";s:3:"טּ";s:4:"טּ";s:3:"יּ";s:4:"יּ";s:3:"ךּ";s:4:"ךּ";s:3:"כּ";s:4:"כּ";s:3:"לּ";s:4:"לּ";s:3:"מּ";s:4:"מּ";s:3:"נּ";s:4:"נּ";s:3:"סּ";s:4:"סּ";s:3:"ףּ";s:4:"ףּ";s:3:"פּ";s:4:"פּ";s:3:"צּ";s:4:"צּ";s:3:"קּ";s:4:"קּ";s:3:"רּ";s:4:"רּ";s:3:"שּ";s:4:"שּ";s:3:"תּ";s:4:"תּ";s:3:"וֹ";s:4:"וֹ";s:3:"בֿ";s:4:"בֿ";s:3:"כֿ";s:4:"כֿ";s:3:"פֿ";s:4:"פֿ";s:3:"ﭏ";s:4:"אל";s:3:"ﭐ";s:2:"ٱ";s:3:"ﭑ";s:2:"ٱ";s:3:"ﭒ";s:2:"ٻ";s:3:"ﭓ";s:2:"ٻ";s:3:"ﭔ";s:2:"ٻ";s:3:"ﭕ";s:2:"ٻ";s:3:"ﭖ";s:2:"پ";s:3:"ﭗ";s:2:"پ";s:3:"ﭘ";s:2:"پ";s:3:"ﭙ";s:2:"پ";s:3:"ﭚ";s:2:"ڀ";s:3:"ﭛ";s:2:"ڀ";s:3:"ﭜ";s:2:"ڀ";s:3:"ﭝ";s:2:"ڀ";s:3:"ﭞ";s:2:"ٺ";s:3:"ﭟ";s:2:"ٺ";s:3:"ﭠ";s:2:"ٺ";s:3:"ﭡ";s:2:"ٺ";s:3:"ﭢ";s:2:"ٿ";s:3:"ﭣ";s:2:"ٿ";s:3:"ﭤ";s:2:"ٿ";s:3:"ﭥ";s:2:"ٿ";s:3:"ﭦ";s:2:"ٹ";s:3:"ﭧ";s:2:"ٹ";s:3:"ﭨ";s:2:"ٹ";s:3:"ﭩ";s:2:"ٹ";s:3:"ﭪ";s:2:"ڤ";s:3:"ﭫ";s:2:"ڤ";s:3:"ﭬ";s:2:"ڤ";s:3:"ﭭ";s:2:"ڤ";s:3:"ﭮ";s:2:"ڦ";s:3:"ﭯ";s:2:"ڦ";s:3:"ﭰ";s:2:"ڦ";s:3:"ﭱ";s:2:"ڦ";s:3:"ﭲ";s:2:"ڄ";s:3:"ﭳ";s:2:"ڄ";s:3:"ﭴ";s:2:"ڄ";s:3:"ﭵ";s:2:"ڄ";s:3:"ﭶ";s:2:"ڃ";s:3:"ﭷ";s:2:"ڃ";s:3:"ﭸ";s:2:"ڃ";s:3:"ﭹ";s:2:"ڃ";s:3:"ﭺ";s:2:"چ";s:3:"ﭻ";s:2:"چ";s:3:"ﭼ";s:2:"چ";s:3:"ﭽ";s:2:"چ";s:3:"ﭾ";s:2:"ڇ";s:3:"ﭿ";s:2:"ڇ";s:3:"ﮀ";s:2:"ڇ";s:3:"ﮁ";s:2:"ڇ";s:3:"ﮂ";s:2:"ڍ";s:3:"ﮃ";s:2:"ڍ";s:3:"ﮄ";s:2:"ڌ";s:3:"ﮅ";s:2:"ڌ";s:3:"ﮆ";s:2:"ڎ";s:3:"ﮇ";s:2:"ڎ";s:3:"ﮈ";s:2:"ڈ";s:3:"ﮉ";s:2:"ڈ";s:3:"ﮊ";s:2:"ژ";s:3:"ﮋ";s:2:"ژ";s:3:"ﮌ";s:2:"ڑ";s:3:"ﮍ";s:2:"ڑ";s:3:"ﮎ";s:2:"ک";s:3:"ﮏ";s:2:"ک";s:3:"ﮐ";s:2:"ک";s:3:"ﮑ";s:2:"ک";s:3:"ﮒ";s:2:"گ";s:3:"ﮓ";s:2:"گ";s:3:"ﮔ";s:2:"گ";s:3:"ﮕ";s:2:"گ";s:3:"ﮖ";s:2:"ڳ";s:3:"ﮗ";s:2:"ڳ";s:3:"ﮘ";s:2:"ڳ";s:3:"ﮙ";s:2:"ڳ";s:3:"ﮚ";s:2:"ڱ";s:3:"ﮛ";s:2:"ڱ";s:3:"ﮜ";s:2:"ڱ";s:3:"ﮝ";s:2:"ڱ";s:3:"ﮞ";s:2:"ں";s:3:"ﮟ";s:2:"ں";s:3:"ﮠ";s:2:"ڻ";s:3:"ﮡ";s:2:"ڻ";s:3:"ﮢ";s:2:"ڻ";s:3:"ﮣ";s:2:"ڻ";s:3:"ﮤ";s:4:"ۀ";s:3:"ﮥ";s:4:"ۀ";s:3:"ﮦ";s:2:"ہ";s:3:"ﮧ";s:2:"ہ";s:3:"ﮨ";s:2:"ہ";s:3:"ﮩ";s:2:"ہ";s:3:"ﮪ";s:2:"ھ";s:3:"ﮫ";s:2:"ھ";s:3:"ﮬ";s:2:"ھ";s:3:"ﮭ";s:2:"ھ";s:3:"ﮮ";s:2:"ے";s:3:"ﮯ";s:2:"ے";s:3:"ﮰ";s:4:"ۓ";s:3:"ﮱ";s:4:"ۓ";s:3:"ﯓ";s:2:"ڭ";s:3:"ﯔ";s:2:"ڭ";s:3:"ﯕ";s:2:"ڭ";s:3:"ﯖ";s:2:"ڭ";s:3:"ﯗ";s:2:"ۇ";s:3:"ﯘ";s:2:"ۇ";s:3:"ﯙ";s:2:"ۆ";s:3:"ﯚ";s:2:"ۆ";s:3:"ﯛ";s:2:"ۈ";s:3:"ﯜ";s:2:"ۈ";s:3:"ﯝ";s:4:"ۇٴ";s:3:"ﯞ";s:2:"ۋ";s:3:"ﯟ";s:2:"ۋ";s:3:"ﯠ";s:2:"ۅ";s:3:"ﯡ";s:2:"ۅ";s:3:"ﯢ";s:2:"ۉ";s:3:"ﯣ";s:2:"ۉ";s:3:"ﯤ";s:2:"ې";s:3:"ﯥ";s:2:"ې";s:3:"ﯦ";s:2:"ې";s:3:"ﯧ";s:2:"ې";s:3:"ﯨ";s:2:"ى";s:3:"ﯩ";s:2:"ى";s:3:"ﯪ";s:6:"ئا";s:3:"ﯫ";s:6:"ئا";s:3:"ﯬ";s:6:"ئە";s:3:"ﯭ";s:6:"ئە";s:3:"ﯮ";s:6:"ئو";s:3:"ﯯ";s:6:"ئو";s:3:"ﯰ";s:6:"ئۇ";s:3:"ﯱ";s:6:"ئۇ";s:3:"ﯲ";s:6:"ئۆ";s:3:"ﯳ";s:6:"ئۆ";s:3:"ﯴ";s:6:"ئۈ";s:3:"ﯵ";s:6:"ئۈ";s:3:"ﯶ";s:6:"ئې";s:3:"ﯷ";s:6:"ئې";s:3:"ﯸ";s:6:"ئې";s:3:"ﯹ";s:6:"ئى";s:3:"ﯺ";s:6:"ئى";s:3:"ﯻ";s:6:"ئى";s:3:"ﯼ";s:2:"ی";s:3:"ﯽ";s:2:"ی";s:3:"ﯾ";s:2:"ی";s:3:"ﯿ";s:2:"ی";s:3:"ﰀ";s:6:"ئج";s:3:"ﰁ";s:6:"ئح";s:3:"ﰂ";s:6:"ئم";s:3:"ﰃ";s:6:"ئى";s:3:"ﰄ";s:6:"ئي";s:3:"ﰅ";s:4:"بج";s:3:"ﰆ";s:4:"بح";s:3:"ﰇ";s:4:"بخ";s:3:"ﰈ";s:4:"بم";s:3:"ﰉ";s:4:"بى";s:3:"ﰊ";s:4:"بي";s:3:"ﰋ";s:4:"تج";s:3:"ﰌ";s:4:"تح";s:3:"ﰍ";s:4:"تخ";s:3:"ﰎ";s:4:"تم";s:3:"ﰏ";s:4:"تى";s:3:"ﰐ";s:4:"تي";s:3:"ﰑ";s:4:"ثج";s:3:"ﰒ";s:4:"ثم";s:3:"ﰓ";s:4:"ثى";s:3:"ﰔ";s:4:"ثي";s:3:"ﰕ";s:4:"جح";s:3:"ﰖ";s:4:"جم";s:3:"ﰗ";s:4:"حج";s:3:"ﰘ";s:4:"حم";s:3:"ﰙ";s:4:"خج";s:3:"ﰚ";s:4:"خح";s:3:"ﰛ";s:4:"خم";s:3:"ﰜ";s:4:"سج";s:3:"ﰝ";s:4:"سح";s:3:"ﰞ";s:4:"سخ";s:3:"ﰟ";s:4:"سم";s:3:"ﰠ";s:4:"صح";s:3:"ﰡ";s:4:"صم";s:3:"ﰢ";s:4:"ضج";s:3:"ﰣ";s:4:"ضح";s:3:"ﰤ";s:4:"ضخ";s:3:"ﰥ";s:4:"ضم";s:3:"ﰦ";s:4:"طح";s:3:"ﰧ";s:4:"طم";s:3:"ﰨ";s:4:"ظم";s:3:"ﰩ";s:4:"عج";s:3:"ﰪ";s:4:"عم";s:3:"ﰫ";s:4:"غج";s:3:"ﰬ";s:4:"غم";s:3:"ﰭ";s:4:"فج";s:3:"ﰮ";s:4:"فح";s:3:"ﰯ";s:4:"فخ";s:3:"ﰰ";s:4:"فم";s:3:"ﰱ";s:4:"فى";s:3:"ﰲ";s:4:"في";s:3:"ﰳ";s:4:"قح";s:3:"ﰴ";s:4:"قم";s:3:"ﰵ";s:4:"قى";s:3:"ﰶ";s:4:"قي";s:3:"ﰷ";s:4:"كا";s:3:"ﰸ";s:4:"كج";s:3:"ﰹ";s:4:"كح";s:3:"ﰺ";s:4:"كخ";s:3:"ﰻ";s:4:"كل";s:3:"ﰼ";s:4:"كم";s:3:"ﰽ";s:4:"كى";s:3:"ﰾ";s:4:"كي";s:3:"ﰿ";s:4:"لج";s:3:"ﱀ";s:4:"لح";s:3:"ﱁ";s:4:"لخ";s:3:"ﱂ";s:4:"لم";s:3:"ﱃ";s:4:"لى";s:3:"ﱄ";s:4:"لي";s:3:"ﱅ";s:4:"مج";s:3:"ﱆ";s:4:"مح";s:3:"ﱇ";s:4:"مخ";s:3:"ﱈ";s:4:"مم";s:3:"ﱉ";s:4:"مى";s:3:"ﱊ";s:4:"مي";s:3:"ﱋ";s:4:"نج";s:3:"ﱌ";s:4:"نح";s:3:"ﱍ";s:4:"نخ";s:3:"ﱎ";s:4:"نم";s:3:"ﱏ";s:4:"نى";s:3:"ﱐ";s:4:"ني";s:3:"ﱑ";s:4:"هج";s:3:"ﱒ";s:4:"هم";s:3:"ﱓ";s:4:"هى";s:3:"ﱔ";s:4:"هي";s:3:"ﱕ";s:4:"يج";s:3:"ﱖ";s:4:"يح";s:3:"ﱗ";s:4:"يخ";s:3:"ﱘ";s:4:"يم";s:3:"ﱙ";s:4:"يى";s:3:"ﱚ";s:4:"يي";s:3:"ﱛ";s:4:"ذٰ";s:3:"ﱜ";s:4:"رٰ";s:3:"ﱝ";s:4:"ىٰ";s:3:"ﱞ";s:5:" ٌّ";s:3:"ﱟ";s:5:" ٍّ";s:3:"ﱠ";s:5:" َّ";s:3:"ﱡ";s:5:" ُّ";s:3:"ﱢ";s:5:" ِّ";s:3:"ﱣ";s:5:" ّٰ";s:3:"ﱤ";s:6:"ئر";s:3:"ﱥ";s:6:"ئز";s:3:"ﱦ";s:6:"ئم";s:3:"ﱧ";s:6:"ئن";s:3:"ﱨ";s:6:"ئى";s:3:"ﱩ";s:6:"ئي";s:3:"ﱪ";s:4:"بر";s:3:"ﱫ";s:4:"بز";s:3:"ﱬ";s:4:"بم";s:3:"ﱭ";s:4:"بن";s:3:"ﱮ";s:4:"بى";s:3:"ﱯ";s:4:"بي";s:3:"ﱰ";s:4:"تر";s:3:"ﱱ";s:4:"تز";s:3:"ﱲ";s:4:"تم";s:3:"ﱳ";s:4:"تن";s:3:"ﱴ";s:4:"تى";s:3:"ﱵ";s:4:"تي";s:3:"ﱶ";s:4:"ثر";s:3:"ﱷ";s:4:"ثز";s:3:"ﱸ";s:4:"ثم";s:3:"ﱹ";s:4:"ثن";s:3:"ﱺ";s:4:"ثى";s:3:"ﱻ";s:4:"ثي";s:3:"ﱼ";s:4:"فى";s:3:"ﱽ";s:4:"في";s:3:"ﱾ";s:4:"قى";s:3:"ﱿ";s:4:"قي";s:3:"ﲀ";s:4:"كا";s:3:"ﲁ";s:4:"كل";s:3:"ﲂ";s:4:"كم";s:3:"ﲃ";s:4:"كى";s:3:"ﲄ";s:4:"كي";s:3:"ﲅ";s:4:"لم";s:3:"ﲆ";s:4:"لى";s:3:"ﲇ";s:4:"لي";s:3:"ﲈ";s:4:"ما";s:3:"ﲉ";s:4:"مم";s:3:"ﲊ";s:4:"نر";s:3:"ﲋ";s:4:"نز";s:3:"ﲌ";s:4:"نم";s:3:"ﲍ";s:4:"نن";s:3:"ﲎ";s:4:"نى";s:3:"ﲏ";s:4:"ني";s:3:"ﲐ";s:4:"ىٰ";s:3:"ﲑ";s:4:"ير";s:3:"ﲒ";s:4:"يز";s:3:"ﲓ";s:4:"يم";s:3:"ﲔ";s:4:"ين";s:3:"ﲕ";s:4:"يى";s:3:"ﲖ";s:4:"يي";s:3:"ﲗ";s:6:"ئج";s:3:"ﲘ";s:6:"ئح";s:3:"ﲙ";s:6:"ئخ";s:3:"ﲚ";s:6:"ئم";s:3:"ﲛ";s:6:"ئه";s:3:"ﲜ";s:4:"بج";s:3:"ﲝ";s:4:"بح";s:3:"ﲞ";s:4:"بخ";s:3:"ﲟ";s:4:"بم";s:3:"ﲠ";s:4:"به";s:3:"ﲡ";s:4:"تج";s:3:"ﲢ";s:4:"تح";s:3:"ﲣ";s:4:"تخ";s:3:"ﲤ";s:4:"تم";s:3:"ﲥ";s:4:"ته";s:3:"ﲦ";s:4:"ثم";s:3:"ﲧ";s:4:"جح";s:3:"ﲨ";s:4:"جم";s:3:"ﲩ";s:4:"حج";s:3:"ﲪ";s:4:"حم";s:3:"ﲫ";s:4:"خج";s:3:"ﲬ";s:4:"خم";s:3:"ﲭ";s:4:"سج";s:3:"ﲮ";s:4:"سح";s:3:"ﲯ";s:4:"سخ";s:3:"ﲰ";s:4:"سم";s:3:"ﲱ";s:4:"صح";s:3:"ﲲ";s:4:"صخ";s:3:"ﲳ";s:4:"صم";s:3:"ﲴ";s:4:"ضج";s:3:"ﲵ";s:4:"ضح";s:3:"ﲶ";s:4:"ضخ";s:3:"ﲷ";s:4:"ضم";s:3:"ﲸ";s:4:"طح";s:3:"ﲹ";s:4:"ظم";s:3:"ﲺ";s:4:"عج";s:3:"ﲻ";s:4:"عم";s:3:"ﲼ";s:4:"غج";s:3:"ﲽ";s:4:"غم";s:3:"ﲾ";s:4:"فج";s:3:"ﲿ";s:4:"فح";s:3:"ﳀ";s:4:"فخ";s:3:"ﳁ";s:4:"فم";s:3:"ﳂ";s:4:"قح";s:3:"ﳃ";s:4:"قم";s:3:"ﳄ";s:4:"كج";s:3:"ﳅ";s:4:"كح";s:3:"ﳆ";s:4:"كخ";s:3:"ﳇ";s:4:"كل";s:3:"ﳈ";s:4:"كم";s:3:"ﳉ";s:4:"لج";s:3:"ﳊ";s:4:"لح";s:3:"ﳋ";s:4:"لخ";s:3:"ﳌ";s:4:"لم";s:3:"ﳍ";s:4:"له";s:3:"ﳎ";s:4:"مج";s:3:"ﳏ";s:4:"مح";s:3:"ﳐ";s:4:"مخ";s:3:"ﳑ";s:4:"مم";s:3:"ﳒ";s:4:"نج";s:3:"ﳓ";s:4:"نح";s:3:"ﳔ";s:4:"نخ";s:3:"ﳕ";s:4:"نم";s:3:"ﳖ";s:4:"نه";s:3:"ﳗ";s:4:"هج";s:3:"ﳘ";s:4:"هم";s:3:"ﳙ";s:4:"هٰ";s:3:"ﳚ";s:4:"يج";s:3:"ﳛ";s:4:"يح";s:3:"ﳜ";s:4:"يخ";s:3:"ﳝ";s:4:"يم";s:3:"ﳞ";s:4:"يه";s:3:"ﳟ";s:6:"ئم";s:3:"ﳠ";s:6:"ئه";s:3:"ﳡ";s:4:"بم";s:3:"ﳢ";s:4:"به";s:3:"ﳣ";s:4:"تم";s:3:"ﳤ";s:4:"ته";s:3:"ﳥ";s:4:"ثم";s:3:"ﳦ";s:4:"ثه";s:3:"ﳧ";s:4:"سم";s:3:"ﳨ";s:4:"سه";s:3:"ﳩ";s:4:"شم";s:3:"ﳪ";s:4:"شه";s:3:"ﳫ";s:4:"كل";s:3:"ﳬ";s:4:"كم";s:3:"ﳭ";s:4:"لم";s:3:"ﳮ";s:4:"نم";s:3:"ﳯ";s:4:"نه";s:3:"ﳰ";s:4:"يم";s:3:"ﳱ";s:4:"يه";s:3:"ﳲ";s:6:"ـَّ";s:3:"ﳳ";s:6:"ـُّ";s:3:"ﳴ";s:6:"ـِّ";s:3:"ﳵ";s:4:"طى";s:3:"ﳶ";s:4:"طي";s:3:"ﳷ";s:4:"عى";s:3:"ﳸ";s:4:"عي";s:3:"ﳹ";s:4:"غى";s:3:"ﳺ";s:4:"غي";s:3:"ﳻ";s:4:"سى";s:3:"ﳼ";s:4:"سي";s:3:"ﳽ";s:4:"شى";s:3:"ﳾ";s:4:"شي";s:3:"ﳿ";s:4:"حى";s:3:"ﴀ";s:4:"حي";s:3:"ﴁ";s:4:"جى";s:3:"ﴂ";s:4:"جي";s:3:"ﴃ";s:4:"خى";s:3:"ﴄ";s:4:"خي";s:3:"ﴅ";s:4:"صى";s:3:"ﴆ";s:4:"صي";s:3:"ﴇ";s:4:"ضى";s:3:"ﴈ";s:4:"ضي";s:3:"ﴉ";s:4:"شج";s:3:"ﴊ";s:4:"شح";s:3:"ﴋ";s:4:"شخ";s:3:"ﴌ";s:4:"شم";s:3:"ﴍ";s:4:"شر";s:3:"ﴎ";s:4:"سر";s:3:"ﴏ";s:4:"صر";s:3:"ﴐ";s:4:"ضر";s:3:"ﴑ";s:4:"طى";s:3:"ﴒ";s:4:"طي";s:3:"ﴓ";s:4:"عى";s:3:"ﴔ";s:4:"عي";s:3:"ﴕ";s:4:"غى";s:3:"ﴖ";s:4:"غي";s:3:"ﴗ";s:4:"سى";s:3:"ﴘ";s:4:"سي";s:3:"ﴙ";s:4:"شى";s:3:"ﴚ";s:4:"شي";s:3:"ﴛ";s:4:"حى";s:3:"ﴜ";s:4:"حي";s:3:"ﴝ";s:4:"جى";s:3:"ﴞ";s:4:"جي";s:3:"ﴟ";s:4:"خى";s:3:"ﴠ";s:4:"خي";s:3:"ﴡ";s:4:"صى";s:3:"ﴢ";s:4:"صي";s:3:"ﴣ";s:4:"ضى";s:3:"ﴤ";s:4:"ضي";s:3:"ﴥ";s:4:"شج";s:3:"ﴦ";s:4:"شح";s:3:"ﴧ";s:4:"شخ";s:3:"ﴨ";s:4:"شم";s:3:"ﴩ";s:4:"شر";s:3:"ﴪ";s:4:"سر";s:3:"ﴫ";s:4:"صر";s:3:"ﴬ";s:4:"ضر";s:3:"ﴭ";s:4:"شج";s:3:"ﴮ";s:4:"شح";s:3:"ﴯ";s:4:"شخ";s:3:"ﴰ";s:4:"شم";s:3:"ﴱ";s:4:"سه";s:3:"ﴲ";s:4:"شه";s:3:"ﴳ";s:4:"طم";s:3:"ﴴ";s:4:"سج";s:3:"ﴵ";s:4:"سح";s:3:"ﴶ";s:4:"سخ";s:3:"ﴷ";s:4:"شج";s:3:"ﴸ";s:4:"شح";s:3:"ﴹ";s:4:"شخ";s:3:"ﴺ";s:4:"طم";s:3:"ﴻ";s:4:"ظم";s:3:"ﴼ";s:4:"اً";s:3:"ﴽ";s:4:"اً";s:3:"ﵐ";s:6:"تجم";s:3:"ﵑ";s:6:"تحج";s:3:"ﵒ";s:6:"تحج";s:3:"ﵓ";s:6:"تحم";s:3:"ﵔ";s:6:"تخم";s:3:"ﵕ";s:6:"تمج";s:3:"ﵖ";s:6:"تمح";s:3:"ﵗ";s:6:"تمخ";s:3:"ﵘ";s:6:"جمح";s:3:"ﵙ";s:6:"جمح";s:3:"ﵚ";s:6:"حمي";s:3:"ﵛ";s:6:"حمى";s:3:"ﵜ";s:6:"سحج";s:3:"ﵝ";s:6:"سجح";s:3:"ﵞ";s:6:"سجى";s:3:"ﵟ";s:6:"سمح";s:3:"ﵠ";s:6:"سمح";s:3:"ﵡ";s:6:"سمج";s:3:"ﵢ";s:6:"سمم";s:3:"ﵣ";s:6:"سمم";s:3:"ﵤ";s:6:"صحح";s:3:"ﵥ";s:6:"صحح";s:3:"ﵦ";s:6:"صمم";s:3:"ﵧ";s:6:"شحم";s:3:"ﵨ";s:6:"شحم";s:3:"ﵩ";s:6:"شجي";s:3:"ﵪ";s:6:"شمخ";s:3:"ﵫ";s:6:"شمخ";s:3:"ﵬ";s:6:"شمم";s:3:"ﵭ";s:6:"شمم";s:3:"ﵮ";s:6:"ضحى";s:3:"ﵯ";s:6:"ضخم";s:3:"ﵰ";s:6:"ضخم";s:3:"ﵱ";s:6:"طمح";s:3:"ﵲ";s:6:"طمح";s:3:"ﵳ";s:6:"طمم";s:3:"ﵴ";s:6:"طمي";s:3:"ﵵ";s:6:"عجم";s:3:"ﵶ";s:6:"عمم";s:3:"ﵷ";s:6:"عمم";s:3:"ﵸ";s:6:"عمى";s:3:"ﵹ";s:6:"غمم";s:3:"ﵺ";s:6:"غمي";s:3:"ﵻ";s:6:"غمى";s:3:"ﵼ";s:6:"فخم";s:3:"ﵽ";s:6:"فخم";s:3:"ﵾ";s:6:"قمح";s:3:"ﵿ";s:6:"قمم";s:3:"ﶀ";s:6:"لحم";s:3:"ﶁ";s:6:"لحي";s:3:"ﶂ";s:6:"لحى";s:3:"ﶃ";s:6:"لجج";s:3:"ﶄ";s:6:"لجج";s:3:"ﶅ";s:6:"لخم";s:3:"ﶆ";s:6:"لخم";s:3:"ﶇ";s:6:"لمح";s:3:"ﶈ";s:6:"لمح";s:3:"ﶉ";s:6:"محج";s:3:"ﶊ";s:6:"محم";s:3:"ﶋ";s:6:"محي";s:3:"ﶌ";s:6:"مجح";s:3:"ﶍ";s:6:"مجم";s:3:"ﶎ";s:6:"مخج";s:3:"ﶏ";s:6:"مخم";s:3:"ﶒ";s:6:"مجخ";s:3:"ﶓ";s:6:"همج";s:3:"ﶔ";s:6:"همم";s:3:"ﶕ";s:6:"نحم";s:3:"ﶖ";s:6:"نحى";s:3:"ﶗ";s:6:"نجم";s:3:"ﶘ";s:6:"نجم";s:3:"ﶙ";s:6:"نجى";s:3:"ﶚ";s:6:"نمي";s:3:"ﶛ";s:6:"نمى";s:3:"ﶜ";s:6:"يمم";s:3:"ﶝ";s:6:"يمم";s:3:"ﶞ";s:6:"بخي";s:3:"ﶟ";s:6:"تجي";s:3:"ﶠ";s:6:"تجى";s:3:"ﶡ";s:6:"تخي";s:3:"ﶢ";s:6:"تخى";s:3:"ﶣ";s:6:"تمي";s:3:"ﶤ";s:6:"تمى";s:3:"ﶥ";s:6:"جمي";s:3:"ﶦ";s:6:"جحى";s:3:"ﶧ";s:6:"جمى";s:3:"ﶨ";s:6:"سخى";s:3:"ﶩ";s:6:"صحي";s:3:"ﶪ";s:6:"شحي";s:3:"ﶫ";s:6:"ضحي";s:3:"ﶬ";s:6:"لجي";s:3:"ﶭ";s:6:"لمي";s:3:"ﶮ";s:6:"يحي";s:3:"ﶯ";s:6:"يجي";s:3:"ﶰ";s:6:"يمي";s:3:"ﶱ";s:6:"ممي";s:3:"ﶲ";s:6:"قمي";s:3:"ﶳ";s:6:"نحي";s:3:"ﶴ";s:6:"قمح";s:3:"ﶵ";s:6:"لحم";s:3:"ﶶ";s:6:"عمي";s:3:"ﶷ";s:6:"كمي";s:3:"ﶸ";s:6:"نجح";s:3:"ﶹ";s:6:"مخي";s:3:"ﶺ";s:6:"لجم";s:3:"ﶻ";s:6:"كمم";s:3:"ﶼ";s:6:"لجم";s:3:"ﶽ";s:6:"نجح";s:3:"ﶾ";s:6:"جحي";s:3:"ﶿ";s:6:"حجي";s:3:"ﷀ";s:6:"مجي";s:3:"ﷁ";s:6:"فمي";s:3:"ﷂ";s:6:"بحي";s:3:"ﷃ";s:6:"كمم";s:3:"ﷄ";s:6:"عجم";s:3:"ﷅ";s:6:"صمم";s:3:"ﷆ";s:6:"سخي";s:3:"ﷇ";s:6:"نجي";s:3:"ﷰ";s:6:"صلے";s:3:"ﷱ";s:6:"قلے";s:3:"ﷲ";s:8:"الله";s:3:"ﷳ";s:8:"اكبر";s:3:"ﷴ";s:8:"محمد";s:3:"ﷵ";s:8:"صلعم";s:3:"ﷶ";s:8:"رسول";s:3:"ﷷ";s:8:"عليه";s:3:"ﷸ";s:8:"وسلم";s:3:"ﷹ";s:6:"صلى";s:3:"ﷺ";s:33:"صلى الله عليه وسلم";s:3:"ﷻ";s:15:"جل جلاله";s:3:"﷼";s:8:"ریال";s:3:"︐";s:1:",";s:3:"︑";s:3:"、";s:3:"︒";s:3:"。";s:3:"︓";s:1:":";s:3:"︔";s:1:";";s:3:"︕";s:1:"!";s:3:"︖";s:1:"?";s:3:"︗";s:3:"〖";s:3:"︘";s:3:"〗";s:3:"︙";s:3:"...";s:3:"︰";s:2:"..";s:3:"︱";s:3:"—";s:3:"︲";s:3:"–";s:3:"︳";s:1:"_";s:3:"︴";s:1:"_";s:3:"︵";s:1:"(";s:3:"︶";s:1:")";s:3:"︷";s:1:"{";s:3:"︸";s:1:"}";s:3:"︹";s:3:"〔";s:3:"︺";s:3:"〕";s:3:"︻";s:3:"【";s:3:"︼";s:3:"】";s:3:"︽";s:3:"《";s:3:"︾";s:3:"》";s:3:"︿";s:3:"〈";s:3:"﹀";s:3:"〉";s:3:"﹁";s:3:"「";s:3:"﹂";s:3:"」";s:3:"﹃";s:3:"『";s:3:"﹄";s:3:"』";s:3:"﹇";s:1:"[";s:3:"﹈";s:1:"]";s:3:"﹉";s:3:" ̅";s:3:"﹊";s:3:" ̅";s:3:"﹋";s:3:" ̅";s:3:"﹌";s:3:" ̅";s:3:"﹍";s:1:"_";s:3:"﹎";s:1:"_";s:3:"﹏";s:1:"_";s:3:"﹐";s:1:",";s:3:"﹑";s:3:"、";s:3:"﹒";s:1:".";s:3:"﹔";s:1:";";s:3:"﹕";s:1:":";s:3:"﹖";s:1:"?";s:3:"﹗";s:1:"!";s:3:"﹘";s:3:"—";s:3:"﹙";s:1:"(";s:3:"﹚";s:1:")";s:3:"﹛";s:1:"{";s:3:"﹜";s:1:"}";s:3:"﹝";s:3:"〔";s:3:"﹞";s:3:"〕";s:3:"﹟";s:1:"#";s:3:"﹠";s:1:"&";s:3:"﹡";s:1:"*";s:3:"﹢";s:1:"+";s:3:"﹣";s:1:"-";s:3:"﹤";s:1:"<";s:3:"﹥";s:1:">";s:3:"﹦";s:1:"=";s:3:"﹨";s:1:"\\";s:3:"﹩";s:1:"$";s:3:"﹪";s:1:"%";s:3:"﹫";s:1:"@";s:3:"ﹰ";s:3:" ً";s:3:"ﹱ";s:4:"ـً";s:3:"ﹲ";s:3:" ٌ";s:3:"ﹴ";s:3:" ٍ";s:3:"ﹶ";s:3:" َ";s:3:"ﹷ";s:4:"ـَ";s:3:"ﹸ";s:3:" ُ";s:3:"ﹹ";s:4:"ـُ";s:3:"ﹺ";s:3:" ِ";s:3:"ﹻ";s:4:"ـِ";s:3:"ﹼ";s:3:" ّ";s:3:"ﹽ";s:4:"ـّ";s:3:"ﹾ";s:3:" ْ";s:3:"ﹿ";s:4:"ـْ";s:3:"ﺀ";s:2:"ء";s:3:"ﺁ";s:4:"آ";s:3:"ﺂ";s:4:"آ";s:3:"ﺃ";s:4:"أ";s:3:"ﺄ";s:4:"أ";s:3:"ﺅ";s:4:"ؤ";s:3:"ﺆ";s:4:"ؤ";s:3:"ﺇ";s:4:"إ";s:3:"ﺈ";s:4:"إ";s:3:"ﺉ";s:4:"ئ";s:3:"ﺊ";s:4:"ئ";s:3:"ﺋ";s:4:"ئ";s:3:"ﺌ";s:4:"ئ";s:3:"ﺍ";s:2:"ا";s:3:"ﺎ";s:2:"ا";s:3:"ﺏ";s:2:"ب";s:3:"ﺐ";s:2:"ب";s:3:"ﺑ";s:2:"ب";s:3:"ﺒ";s:2:"ب";s:3:"ﺓ";s:2:"ة";s:3:"ﺔ";s:2:"ة";s:3:"ﺕ";s:2:"ت";s:3:"ﺖ";s:2:"ت";s:3:"ﺗ";s:2:"ت";s:3:"ﺘ";s:2:"ت";s:3:"ﺙ";s:2:"ث";s:3:"ﺚ";s:2:"ث";s:3:"ﺛ";s:2:"ث";s:3:"ﺜ";s:2:"ث";s:3:"ﺝ";s:2:"ج";s:3:"ﺞ";s:2:"ج";s:3:"ﺟ";s:2:"ج";s:3:"ﺠ";s:2:"ج";s:3:"ﺡ";s:2:"ح";s:3:"ﺢ";s:2:"ح";s:3:"ﺣ";s:2:"ح";s:3:"ﺤ";s:2:"ح";s:3:"ﺥ";s:2:"خ";s:3:"ﺦ";s:2:"خ";s:3:"ﺧ";s:2:"خ";s:3:"ﺨ";s:2:"خ";s:3:"ﺩ";s:2:"د";s:3:"ﺪ";s:2:"د";s:3:"ﺫ";s:2:"ذ";s:3:"ﺬ";s:2:"ذ";s:3:"ﺭ";s:2:"ر";s:3:"ﺮ";s:2:"ر";s:3:"ﺯ";s:2:"ز";s:3:"ﺰ";s:2:"ز";s:3:"ﺱ";s:2:"س";s:3:"ﺲ";s:2:"س";s:3:"ﺳ";s:2:"س";s:3:"ﺴ";s:2:"س";s:3:"ﺵ";s:2:"ش";s:3:"ﺶ";s:2:"ش";s:3:"ﺷ";s:2:"ش";s:3:"ﺸ";s:2:"ش";s:3:"ﺹ";s:2:"ص";s:3:"ﺺ";s:2:"ص";s:3:"ﺻ";s:2:"ص";s:3:"ﺼ";s:2:"ص";s:3:"ﺽ";s:2:"ض";s:3:"ﺾ";s:2:"ض";s:3:"ﺿ";s:2:"ض";s:3:"ﻀ";s:2:"ض";s:3:"ﻁ";s:2:"ط";s:3:"ﻂ";s:2:"ط";s:3:"ﻃ";s:2:"ط";s:3:"ﻄ";s:2:"ط";s:3:"ﻅ";s:2:"ظ";s:3:"ﻆ";s:2:"ظ";s:3:"ﻇ";s:2:"ظ";s:3:"ﻈ";s:2:"ظ";s:3:"ﻉ";s:2:"ع";s:3:"ﻊ";s:2:"ع";s:3:"ﻋ";s:2:"ع";s:3:"ﻌ";s:2:"ع";s:3:"ﻍ";s:2:"غ";s:3:"ﻎ";s:2:"غ";s:3:"ﻏ";s:2:"غ";s:3:"ﻐ";s:2:"غ";s:3:"ﻑ";s:2:"ف";s:3:"ﻒ";s:2:"ف";s:3:"ﻓ";s:2:"ف";s:3:"ﻔ";s:2:"ف";s:3:"ﻕ";s:2:"ق";s:3:"ﻖ";s:2:"ق";s:3:"ﻗ";s:2:"ق";s:3:"ﻘ";s:2:"ق";s:3:"ﻙ";s:2:"ك";s:3:"ﻚ";s:2:"ك";s:3:"ﻛ";s:2:"ك";s:3:"ﻜ";s:2:"ك";s:3:"ﻝ";s:2:"ل";s:3:"ﻞ";s:2:"ل";s:3:"ﻟ";s:2:"ل";s:3:"ﻠ";s:2:"ل";s:3:"ﻡ";s:2:"م";s:3:"ﻢ";s:2:"م";s:3:"ﻣ";s:2:"م";s:3:"ﻤ";s:2:"م";s:3:"ﻥ";s:2:"ن";s:3:"ﻦ";s:2:"ن";s:3:"ﻧ";s:2:"ن";s:3:"ﻨ";s:2:"ن";s:3:"ﻩ";s:2:"ه";s:3:"ﻪ";s:2:"ه";s:3:"ﻫ";s:2:"ه";s:3:"ﻬ";s:2:"ه";s:3:"ﻭ";s:2:"و";s:3:"ﻮ";s:2:"و";s:3:"ﻯ";s:2:"ى";s:3:"ﻰ";s:2:"ى";s:3:"ﻱ";s:2:"ي";s:3:"ﻲ";s:2:"ي";s:3:"ﻳ";s:2:"ي";s:3:"ﻴ";s:2:"ي";s:3:"ﻵ";s:6:"لآ";s:3:"ﻶ";s:6:"لآ";s:3:"ﻷ";s:6:"لأ";s:3:"ﻸ";s:6:"لأ";s:3:"ﻹ";s:6:"لإ";s:3:"ﻺ";s:6:"لإ";s:3:"ﻻ";s:4:"لا";s:3:"ﻼ";s:4:"لا";s:3:"!";s:1:"!";s:3:""";s:1:""";s:3:"#";s:1:"#";s:3:"$";s:1:"$";s:3:"%";s:1:"%";s:3:"&";s:1:"&";s:3:"'";s:1:"\'";s:3:"(";s:1:"(";s:3:")";s:1:")";s:3:"*";s:1:"*";s:3:"+";s:1:"+";s:3:",";s:1:",";s:3:"-";s:1:"-";s:3:".";s:1:".";s:3:"/";s:1:"/";s:3:"0";s:1:"0";s:3:"1";s:1:"1";s:3:"2";s:1:"2";s:3:"3";s:1:"3";s:3:"4";s:1:"4";s:3:"5";s:1:"5";s:3:"6";s:1:"6";s:3:"7";s:1:"7";s:3:"8";s:1:"8";s:3:"9";s:1:"9";s:3:":";s:1:":";s:3:";";s:1:";";s:3:"<";s:1:"<";s:3:"=";s:1:"=";s:3:">";s:1:">";s:3:"?";s:1:"?";s:3:"@";s:1:"@";s:3:"A";s:1:"A";s:3:"B";s:1:"B";s:3:"C";s:1:"C";s:3:"D";s:1:"D";s:3:"E";s:1:"E";s:3:"F";s:1:"F";s:3:"G";s:1:"G";s:3:"H";s:1:"H";s:3:"I";s:1:"I";s:3:"J";s:1:"J";s:3:"K";s:1:"K";s:3:"L";s:1:"L";s:3:"M";s:1:"M";s:3:"N";s:1:"N";s:3:"O";s:1:"O";s:3:"P";s:1:"P";s:3:"Q";s:1:"Q";s:3:"R";s:1:"R";s:3:"S";s:1:"S";s:3:"T";s:1:"T";s:3:"U";s:1:"U";s:3:"V";s:1:"V";s:3:"W";s:1:"W";s:3:"X";s:1:"X";s:3:"Y";s:1:"Y";s:3:"Z";s:1:"Z";s:3:"[";s:1:"[";s:3:"\";s:1:"\\";s:3:"]";s:1:"]";s:3:"^";s:1:"^";s:3:"_";s:1:"_";s:3:"`";s:1:"`";s:3:"a";s:1:"a";s:3:"b";s:1:"b";s:3:"c";s:1:"c";s:3:"d";s:1:"d";s:3:"e";s:1:"e";s:3:"f";s:1:"f";s:3:"g";s:1:"g";s:3:"h";s:1:"h";s:3:"i";s:1:"i";s:3:"j";s:1:"j";s:3:"k";s:1:"k";s:3:"l";s:1:"l";s:3:"m";s:1:"m";s:3:"n";s:1:"n";s:3:"o";s:1:"o";s:3:"p";s:1:"p";s:3:"q";s:1:"q";s:3:"r";s:1:"r";s:3:"s";s:1:"s";s:3:"t";s:1:"t";s:3:"u";s:1:"u";s:3:"v";s:1:"v";s:3:"w";s:1:"w";s:3:"x";s:1:"x";s:3:"y";s:1:"y";s:3:"z";s:1:"z";s:3:"{";s:1:"{";s:3:"|";s:1:"|";s:3:"}";s:1:"}";s:3:"~";s:1:"~";s:3:"⦅";s:3:"⦅";s:3:"⦆";s:3:"⦆";s:3:"。";s:3:"。";s:3:"「";s:3:"「";s:3:"」";s:3:"」";s:3:"、";s:3:"、";s:3:"・";s:3:"・";s:3:"ヲ";s:3:"ヲ";s:3:"ァ";s:3:"ァ";s:3:"ィ";s:3:"ィ";s:3:"ゥ";s:3:"ゥ";s:3:"ェ";s:3:"ェ";s:3:"ォ";s:3:"ォ";s:3:"ャ";s:3:"ャ";s:3:"ュ";s:3:"ュ";s:3:"ョ";s:3:"ョ";s:3:"ッ";s:3:"ッ";s:3:"ー";s:3:"ー";s:3:"ア";s:3:"ア";s:3:"イ";s:3:"イ";s:3:"ウ";s:3:"ウ";s:3:"エ";s:3:"エ";s:3:"オ";s:3:"オ";s:3:"カ";s:3:"カ";s:3:"キ";s:3:"キ";s:3:"ク";s:3:"ク";s:3:"ケ";s:3:"ケ";s:3:"コ";s:3:"コ";s:3:"サ";s:3:"サ";s:3:"シ";s:3:"シ";s:3:"ス";s:3:"ス";s:3:"セ";s:3:"セ";s:3:"ソ";s:3:"ソ";s:3:"タ";s:3:"タ";s:3:"チ";s:3:"チ";s:3:"ツ";s:3:"ツ";s:3:"テ";s:3:"テ";s:3:"ト";s:3:"ト";s:3:"ナ";s:3:"ナ";s:3:"ニ";s:3:"ニ";s:3:"ヌ";s:3:"ヌ";s:3:"ネ";s:3:"ネ";s:3:"ノ";s:3:"ノ";s:3:"ハ";s:3:"ハ";s:3:"ヒ";s:3:"ヒ";s:3:"フ";s:3:"フ";s:3:"ヘ";s:3:"ヘ";s:3:"ホ";s:3:"ホ";s:3:"マ";s:3:"マ";s:3:"ミ";s:3:"ミ";s:3:"ム";s:3:"ム";s:3:"メ";s:3:"メ";s:3:"モ";s:3:"モ";s:3:"ヤ";s:3:"ヤ";s:3:"ユ";s:3:"ユ";s:3:"ヨ";s:3:"ヨ";s:3:"ラ";s:3:"ラ";s:3:"リ";s:3:"リ";s:3:"ル";s:3:"ル";s:3:"レ";s:3:"レ";s:3:"ロ";s:3:"ロ";s:3:"ワ";s:3:"ワ";s:3:"ン";s:3:"ン";s:3:"゙";s:3:"゙";s:3:"゚";s:3:"゚";s:3:"ᅠ";s:3:"ᅠ";s:3:"ᄀ";s:3:"ᄀ";s:3:"ᄁ";s:3:"ᄁ";s:3:"ᆪ";s:3:"ᆪ";s:3:"ᄂ";s:3:"ᄂ";s:3:"ᆬ";s:3:"ᆬ";s:3:"ᆭ";s:3:"ᆭ";s:3:"ᄃ";s:3:"ᄃ";s:3:"ᄄ";s:3:"ᄄ";s:3:"ᄅ";s:3:"ᄅ";s:3:"ᆰ";s:3:"ᆰ";s:3:"ᆱ";s:3:"ᆱ";s:3:"ᆲ";s:3:"ᆲ";s:3:"ᆳ";s:3:"ᆳ";s:3:"ᆴ";s:3:"ᆴ";s:3:"ᆵ";s:3:"ᆵ";s:3:"ᄚ";s:3:"ᄚ";s:3:"ᄆ";s:3:"ᄆ";s:3:"ᄇ";s:3:"ᄇ";s:3:"ᄈ";s:3:"ᄈ";s:3:"ᄡ";s:3:"ᄡ";s:3:"ᄉ";s:3:"ᄉ";s:3:"ᄊ";s:3:"ᄊ";s:3:"ᄋ";s:3:"ᄋ";s:3:"ᄌ";s:3:"ᄌ";s:3:"ᄍ";s:3:"ᄍ";s:3:"ᄎ";s:3:"ᄎ";s:3:"ᄏ";s:3:"ᄏ";s:3:"ᄐ";s:3:"ᄐ";s:3:"ᄑ";s:3:"ᄑ";s:3:"ᄒ";s:3:"ᄒ";s:3:"ᅡ";s:3:"ᅡ";s:3:"ᅢ";s:3:"ᅢ";s:3:"ᅣ";s:3:"ᅣ";s:3:"ᅤ";s:3:"ᅤ";s:3:"ᅥ";s:3:"ᅥ";s:3:"ᅦ";s:3:"ᅦ";s:3:"ᅧ";s:3:"ᅧ";s:3:"ᅨ";s:3:"ᅨ";s:3:"ᅩ";s:3:"ᅩ";s:3:"ᅪ";s:3:"ᅪ";s:3:"ᅫ";s:3:"ᅫ";s:3:"ᅬ";s:3:"ᅬ";s:3:"ᅭ";s:3:"ᅭ";s:3:"ᅮ";s:3:"ᅮ";s:3:"ᅯ";s:3:"ᅯ";s:3:"ᅰ";s:3:"ᅰ";s:3:"ᅱ";s:3:"ᅱ";s:3:"ᅲ";s:3:"ᅲ";s:3:"ᅳ";s:3:"ᅳ";s:3:"ᅴ";s:3:"ᅴ";s:3:"ᅵ";s:3:"ᅵ";s:3:"¢";s:2:"¢";s:3:"£";s:2:"£";s:3:"¬";s:2:"¬";s:3:" ̄";s:3:" ̄";s:3:"¦";s:2:"¦";s:3:"¥";s:2:"¥";s:3:"₩";s:3:"₩";s:3:"│";s:3:"│";s:3:"←";s:3:"←";s:3:"↑";s:3:"↑";s:3:"→";s:3:"→";s:3:"↓";s:3:"↓";s:3:"■";s:3:"■";s:3:"○";s:3:"○";s:4:"𑂚";s:8:"𑂚";s:4:"𑂜";s:8:"𑂜";s:4:"𑂫";s:8:"𑂫";s:4:"𝅗𝅥";s:8:"𝅗𝅥";s:4:"𝅘𝅥";s:8:"𝅘𝅥";s:4:"𝅘𝅥𝅮";s:12:"𝅘𝅥𝅮";s:4:"𝅘𝅥𝅯";s:12:"𝅘𝅥𝅯";s:4:"𝅘𝅥𝅰";s:12:"𝅘𝅥𝅰";s:4:"𝅘𝅥𝅱";s:12:"𝅘𝅥𝅱";s:4:"𝅘𝅥𝅲";s:12:"𝅘𝅥𝅲";s:4:"𝆹𝅥";s:8:"𝆹𝅥";s:4:"𝆺𝅥";s:8:"𝆺𝅥";s:4:"𝆹𝅥𝅮";s:12:"𝆹𝅥𝅮";s:4:"𝆺𝅥𝅮";s:12:"𝆺𝅥𝅮";s:4:"𝆹𝅥𝅯";s:12:"𝆹𝅥𝅯";s:4:"𝆺𝅥𝅯";s:12:"𝆺𝅥𝅯";s:4:"𝐀";s:1:"A";s:4:"𝐁";s:1:"B";s:4:"𝐂";s:1:"C";s:4:"𝐃";s:1:"D";s:4:"𝐄";s:1:"E";s:4:"𝐅";s:1:"F";s:4:"𝐆";s:1:"G";s:4:"𝐇";s:1:"H";s:4:"𝐈";s:1:"I";s:4:"𝐉";s:1:"J";s:4:"𝐊";s:1:"K";s:4:"𝐋";s:1:"L";s:4:"𝐌";s:1:"M";s:4:"𝐍";s:1:"N";s:4:"𝐎";s:1:"O";s:4:"𝐏";s:1:"P";s:4:"𝐐";s:1:"Q";s:4:"𝐑";s:1:"R";s:4:"𝐒";s:1:"S";s:4:"𝐓";s:1:"T";s:4:"𝐔";s:1:"U";s:4:"𝐕";s:1:"V";s:4:"𝐖";s:1:"W";s:4:"𝐗";s:1:"X";s:4:"𝐘";s:1:"Y";s:4:"𝐙";s:1:"Z";s:4:"𝐚";s:1:"a";s:4:"𝐛";s:1:"b";s:4:"𝐜";s:1:"c";s:4:"𝐝";s:1:"d";s:4:"𝐞";s:1:"e";s:4:"𝐟";s:1:"f";s:4:"𝐠";s:1:"g";s:4:"𝐡";s:1:"h";s:4:"𝐢";s:1:"i";s:4:"𝐣";s:1:"j";s:4:"𝐤";s:1:"k";s:4:"𝐥";s:1:"l";s:4:"𝐦";s:1:"m";s:4:"𝐧";s:1:"n";s:4:"𝐨";s:1:"o";s:4:"𝐩";s:1:"p";s:4:"𝐪";s:1:"q";s:4:"𝐫";s:1:"r";s:4:"𝐬";s:1:"s";s:4:"𝐭";s:1:"t";s:4:"𝐮";s:1:"u";s:4:"𝐯";s:1:"v";s:4:"𝐰";s:1:"w";s:4:"𝐱";s:1:"x";s:4:"𝐲";s:1:"y";s:4:"𝐳";s:1:"z";s:4:"𝐴";s:1:"A";s:4:"𝐵";s:1:"B";s:4:"𝐶";s:1:"C";s:4:"𝐷";s:1:"D";s:4:"𝐸";s:1:"E";s:4:"𝐹";s:1:"F";s:4:"𝐺";s:1:"G";s:4:"𝐻";s:1:"H";s:4:"𝐼";s:1:"I";s:4:"𝐽";s:1:"J";s:4:"𝐾";s:1:"K";s:4:"𝐿";s:1:"L";s:4:"𝑀";s:1:"M";s:4:"𝑁";s:1:"N";s:4:"𝑂";s:1:"O";s:4:"𝑃";s:1:"P";s:4:"𝑄";s:1:"Q";s:4:"𝑅";s:1:"R";s:4:"𝑆";s:1:"S";s:4:"𝑇";s:1:"T";s:4:"𝑈";s:1:"U";s:4:"𝑉";s:1:"V";s:4:"𝑊";s:1:"W";s:4:"𝑋";s:1:"X";s:4:"𝑌";s:1:"Y";s:4:"𝑍";s:1:"Z";s:4:"𝑎";s:1:"a";s:4:"𝑏";s:1:"b";s:4:"𝑐";s:1:"c";s:4:"𝑑";s:1:"d";s:4:"𝑒";s:1:"e";s:4:"𝑓";s:1:"f";s:4:"𝑔";s:1:"g";s:4:"𝑖";s:1:"i";s:4:"𝑗";s:1:"j";s:4:"𝑘";s:1:"k";s:4:"𝑙";s:1:"l";s:4:"𝑚";s:1:"m";s:4:"𝑛";s:1:"n";s:4:"𝑜";s:1:"o";s:4:"𝑝";s:1:"p";s:4:"𝑞";s:1:"q";s:4:"𝑟";s:1:"r";s:4:"𝑠";s:1:"s";s:4:"𝑡";s:1:"t";s:4:"𝑢";s:1:"u";s:4:"𝑣";s:1:"v";s:4:"𝑤";s:1:"w";s:4:"𝑥";s:1:"x";s:4:"𝑦";s:1:"y";s:4:"𝑧";s:1:"z";s:4:"𝑨";s:1:"A";s:4:"𝑩";s:1:"B";s:4:"𝑪";s:1:"C";s:4:"𝑫";s:1:"D";s:4:"𝑬";s:1:"E";s:4:"𝑭";s:1:"F";s:4:"𝑮";s:1:"G";s:4:"𝑯";s:1:"H";s:4:"𝑰";s:1:"I";s:4:"𝑱";s:1:"J";s:4:"𝑲";s:1:"K";s:4:"𝑳";s:1:"L";s:4:"𝑴";s:1:"M";s:4:"𝑵";s:1:"N";s:4:"𝑶";s:1:"O";s:4:"𝑷";s:1:"P";s:4:"𝑸";s:1:"Q";s:4:"𝑹";s:1:"R";s:4:"𝑺";s:1:"S";s:4:"𝑻";s:1:"T";s:4:"𝑼";s:1:"U";s:4:"𝑽";s:1:"V";s:4:"𝑾";s:1:"W";s:4:"𝑿";s:1:"X";s:4:"𝒀";s:1:"Y";s:4:"𝒁";s:1:"Z";s:4:"𝒂";s:1:"a";s:4:"𝒃";s:1:"b";s:4:"𝒄";s:1:"c";s:4:"𝒅";s:1:"d";s:4:"𝒆";s:1:"e";s:4:"𝒇";s:1:"f";s:4:"𝒈";s:1:"g";s:4:"𝒉";s:1:"h";s:4:"𝒊";s:1:"i";s:4:"𝒋";s:1:"j";s:4:"𝒌";s:1:"k";s:4:"𝒍";s:1:"l";s:4:"𝒎";s:1:"m";s:4:"𝒏";s:1:"n";s:4:"𝒐";s:1:"o";s:4:"𝒑";s:1:"p";s:4:"𝒒";s:1:"q";s:4:"𝒓";s:1:"r";s:4:"𝒔";s:1:"s";s:4:"𝒕";s:1:"t";s:4:"𝒖";s:1:"u";s:4:"𝒗";s:1:"v";s:4:"𝒘";s:1:"w";s:4:"𝒙";s:1:"x";s:4:"𝒚";s:1:"y";s:4:"𝒛";s:1:"z";s:4:"𝒜";s:1:"A";s:4:"𝒞";s:1:"C";s:4:"𝒟";s:1:"D";s:4:"𝒢";s:1:"G";s:4:"𝒥";s:1:"J";s:4:"𝒦";s:1:"K";s:4:"𝒩";s:1:"N";s:4:"𝒪";s:1:"O";s:4:"𝒫";s:1:"P";s:4:"𝒬";s:1:"Q";s:4:"𝒮";s:1:"S";s:4:"𝒯";s:1:"T";s:4:"𝒰";s:1:"U";s:4:"𝒱";s:1:"V";s:4:"𝒲";s:1:"W";s:4:"𝒳";s:1:"X";s:4:"𝒴";s:1:"Y";s:4:"𝒵";s:1:"Z";s:4:"𝒶";s:1:"a";s:4:"𝒷";s:1:"b";s:4:"𝒸";s:1:"c";s:4:"𝒹";s:1:"d";s:4:"𝒻";s:1:"f";s:4:"𝒽";s:1:"h";s:4:"𝒾";s:1:"i";s:4:"𝒿";s:1:"j";s:4:"𝓀";s:1:"k";s:4:"𝓁";s:1:"l";s:4:"𝓂";s:1:"m";s:4:"𝓃";s:1:"n";s:4:"𝓅";s:1:"p";s:4:"𝓆";s:1:"q";s:4:"𝓇";s:1:"r";s:4:"𝓈";s:1:"s";s:4:"𝓉";s:1:"t";s:4:"𝓊";s:1:"u";s:4:"𝓋";s:1:"v";s:4:"𝓌";s:1:"w";s:4:"𝓍";s:1:"x";s:4:"𝓎";s:1:"y";s:4:"𝓏";s:1:"z";s:4:"𝓐";s:1:"A";s:4:"𝓑";s:1:"B";s:4:"𝓒";s:1:"C";s:4:"𝓓";s:1:"D";s:4:"𝓔";s:1:"E";s:4:"𝓕";s:1:"F";s:4:"𝓖";s:1:"G";s:4:"𝓗";s:1:"H";s:4:"𝓘";s:1:"I";s:4:"𝓙";s:1:"J";s:4:"𝓚";s:1:"K";s:4:"𝓛";s:1:"L";s:4:"𝓜";s:1:"M";s:4:"𝓝";s:1:"N";s:4:"𝓞";s:1:"O";s:4:"𝓟";s:1:"P";s:4:"𝓠";s:1:"Q";s:4:"𝓡";s:1:"R";s:4:"𝓢";s:1:"S";s:4:"𝓣";s:1:"T";s:4:"𝓤";s:1:"U";s:4:"𝓥";s:1:"V";s:4:"𝓦";s:1:"W";s:4:"𝓧";s:1:"X";s:4:"𝓨";s:1:"Y";s:4:"𝓩";s:1:"Z";s:4:"𝓪";s:1:"a";s:4:"𝓫";s:1:"b";s:4:"𝓬";s:1:"c";s:4:"𝓭";s:1:"d";s:4:"𝓮";s:1:"e";s:4:"𝓯";s:1:"f";s:4:"𝓰";s:1:"g";s:4:"𝓱";s:1:"h";s:4:"𝓲";s:1:"i";s:4:"𝓳";s:1:"j";s:4:"𝓴";s:1:"k";s:4:"𝓵";s:1:"l";s:4:"𝓶";s:1:"m";s:4:"𝓷";s:1:"n";s:4:"𝓸";s:1:"o";s:4:"𝓹";s:1:"p";s:4:"𝓺";s:1:"q";s:4:"𝓻";s:1:"r";s:4:"𝓼";s:1:"s";s:4:"𝓽";s:1:"t";s:4:"𝓾";s:1:"u";s:4:"𝓿";s:1:"v";s:4:"𝔀";s:1:"w";s:4:"𝔁";s:1:"x";s:4:"𝔂";s:1:"y";s:4:"𝔃";s:1:"z";s:4:"𝔄";s:1:"A";s:4:"𝔅";s:1:"B";s:4:"𝔇";s:1:"D";s:4:"𝔈";s:1:"E";s:4:"𝔉";s:1:"F";s:4:"𝔊";s:1:"G";s:4:"𝔍";s:1:"J";s:4:"𝔎";s:1:"K";s:4:"𝔏";s:1:"L";s:4:"𝔐";s:1:"M";s:4:"𝔑";s:1:"N";s:4:"𝔒";s:1:"O";s:4:"𝔓";s:1:"P";s:4:"𝔔";s:1:"Q";s:4:"𝔖";s:1:"S";s:4:"𝔗";s:1:"T";s:4:"𝔘";s:1:"U";s:4:"𝔙";s:1:"V";s:4:"𝔚";s:1:"W";s:4:"𝔛";s:1:"X";s:4:"𝔜";s:1:"Y";s:4:"𝔞";s:1:"a";s:4:"𝔟";s:1:"b";s:4:"𝔠";s:1:"c";s:4:"𝔡";s:1:"d";s:4:"𝔢";s:1:"e";s:4:"𝔣";s:1:"f";s:4:"𝔤";s:1:"g";s:4:"𝔥";s:1:"h";s:4:"𝔦";s:1:"i";s:4:"𝔧";s:1:"j";s:4:"𝔨";s:1:"k";s:4:"𝔩";s:1:"l";s:4:"𝔪";s:1:"m";s:4:"𝔫";s:1:"n";s:4:"𝔬";s:1:"o";s:4:"𝔭";s:1:"p";s:4:"𝔮";s:1:"q";s:4:"𝔯";s:1:"r";s:4:"𝔰";s:1:"s";s:4:"𝔱";s:1:"t";s:4:"𝔲";s:1:"u";s:4:"𝔳";s:1:"v";s:4:"𝔴";s:1:"w";s:4:"𝔵";s:1:"x";s:4:"𝔶";s:1:"y";s:4:"𝔷";s:1:"z";s:4:"𝔸";s:1:"A";s:4:"𝔹";s:1:"B";s:4:"𝔻";s:1:"D";s:4:"𝔼";s:1:"E";s:4:"𝔽";s:1:"F";s:4:"𝔾";s:1:"G";s:4:"𝕀";s:1:"I";s:4:"𝕁";s:1:"J";s:4:"𝕂";s:1:"K";s:4:"𝕃";s:1:"L";s:4:"𝕄";s:1:"M";s:4:"𝕆";s:1:"O";s:4:"𝕊";s:1:"S";s:4:"𝕋";s:1:"T";s:4:"𝕌";s:1:"U";s:4:"𝕍";s:1:"V";s:4:"𝕎";s:1:"W";s:4:"𝕏";s:1:"X";s:4:"𝕐";s:1:"Y";s:4:"𝕒";s:1:"a";s:4:"𝕓";s:1:"b";s:4:"𝕔";s:1:"c";s:4:"𝕕";s:1:"d";s:4:"𝕖";s:1:"e";s:4:"𝕗";s:1:"f";s:4:"𝕘";s:1:"g";s:4:"𝕙";s:1:"h";s:4:"𝕚";s:1:"i";s:4:"𝕛";s:1:"j";s:4:"𝕜";s:1:"k";s:4:"𝕝";s:1:"l";s:4:"𝕞";s:1:"m";s:4:"𝕟";s:1:"n";s:4:"𝕠";s:1:"o";s:4:"𝕡";s:1:"p";s:4:"𝕢";s:1:"q";s:4:"𝕣";s:1:"r";s:4:"𝕤";s:1:"s";s:4:"𝕥";s:1:"t";s:4:"𝕦";s:1:"u";s:4:"𝕧";s:1:"v";s:4:"𝕨";s:1:"w";s:4:"𝕩";s:1:"x";s:4:"𝕪";s:1:"y";s:4:"𝕫";s:1:"z";s:4:"𝕬";s:1:"A";s:4:"𝕭";s:1:"B";s:4:"𝕮";s:1:"C";s:4:"𝕯";s:1:"D";s:4:"𝕰";s:1:"E";s:4:"𝕱";s:1:"F";s:4:"𝕲";s:1:"G";s:4:"𝕳";s:1:"H";s:4:"𝕴";s:1:"I";s:4:"𝕵";s:1:"J";s:4:"𝕶";s:1:"K";s:4:"𝕷";s:1:"L";s:4:"𝕸";s:1:"M";s:4:"𝕹";s:1:"N";s:4:"𝕺";s:1:"O";s:4:"𝕻";s:1:"P";s:4:"𝕼";s:1:"Q";s:4:"𝕽";s:1:"R";s:4:"𝕾";s:1:"S";s:4:"𝕿";s:1:"T";s:4:"𝖀";s:1:"U";s:4:"𝖁";s:1:"V";s:4:"𝖂";s:1:"W";s:4:"𝖃";s:1:"X";s:4:"𝖄";s:1:"Y";s:4:"𝖅";s:1:"Z";s:4:"𝖆";s:1:"a";s:4:"𝖇";s:1:"b";s:4:"𝖈";s:1:"c";s:4:"𝖉";s:1:"d";s:4:"𝖊";s:1:"e";s:4:"𝖋";s:1:"f";s:4:"𝖌";s:1:"g";s:4:"𝖍";s:1:"h";s:4:"𝖎";s:1:"i";s:4:"𝖏";s:1:"j";s:4:"𝖐";s:1:"k";s:4:"𝖑";s:1:"l";s:4:"𝖒";s:1:"m";s:4:"𝖓";s:1:"n";s:4:"𝖔";s:1:"o";s:4:"𝖕";s:1:"p";s:4:"𝖖";s:1:"q";s:4:"𝖗";s:1:"r";s:4:"𝖘";s:1:"s";s:4:"𝖙";s:1:"t";s:4:"𝖚";s:1:"u";s:4:"𝖛";s:1:"v";s:4:"𝖜";s:1:"w";s:4:"𝖝";s:1:"x";s:4:"𝖞";s:1:"y";s:4:"𝖟";s:1:"z";s:4:"𝖠";s:1:"A";s:4:"𝖡";s:1:"B";s:4:"𝖢";s:1:"C";s:4:"𝖣";s:1:"D";s:4:"𝖤";s:1:"E";s:4:"𝖥";s:1:"F";s:4:"𝖦";s:1:"G";s:4:"𝖧";s:1:"H";s:4:"𝖨";s:1:"I";s:4:"𝖩";s:1:"J";s:4:"𝖪";s:1:"K";s:4:"𝖫";s:1:"L";s:4:"𝖬";s:1:"M";s:4:"𝖭";s:1:"N";s:4:"𝖮";s:1:"O";s:4:"𝖯";s:1:"P";s:4:"𝖰";s:1:"Q";s:4:"𝖱";s:1:"R";s:4:"𝖲";s:1:"S";s:4:"𝖳";s:1:"T";s:4:"𝖴";s:1:"U";s:4:"𝖵";s:1:"V";s:4:"𝖶";s:1:"W";s:4:"𝖷";s:1:"X";s:4:"𝖸";s:1:"Y";s:4:"𝖹";s:1:"Z";s:4:"𝖺";s:1:"a";s:4:"𝖻";s:1:"b";s:4:"𝖼";s:1:"c";s:4:"𝖽";s:1:"d";s:4:"𝖾";s:1:"e";s:4:"𝖿";s:1:"f";s:4:"𝗀";s:1:"g";s:4:"𝗁";s:1:"h";s:4:"𝗂";s:1:"i";s:4:"𝗃";s:1:"j";s:4:"𝗄";s:1:"k";s:4:"𝗅";s:1:"l";s:4:"𝗆";s:1:"m";s:4:"𝗇";s:1:"n";s:4:"𝗈";s:1:"o";s:4:"𝗉";s:1:"p";s:4:"𝗊";s:1:"q";s:4:"𝗋";s:1:"r";s:4:"𝗌";s:1:"s";s:4:"𝗍";s:1:"t";s:4:"𝗎";s:1:"u";s:4:"𝗏";s:1:"v";s:4:"𝗐";s:1:"w";s:4:"𝗑";s:1:"x";s:4:"𝗒";s:1:"y";s:4:"𝗓";s:1:"z";s:4:"𝗔";s:1:"A";s:4:"𝗕";s:1:"B";s:4:"𝗖";s:1:"C";s:4:"𝗗";s:1:"D";s:4:"𝗘";s:1:"E";s:4:"𝗙";s:1:"F";s:4:"𝗚";s:1:"G";s:4:"𝗛";s:1:"H";s:4:"𝗜";s:1:"I";s:4:"𝗝";s:1:"J";s:4:"𝗞";s:1:"K";s:4:"𝗟";s:1:"L";s:4:"𝗠";s:1:"M";s:4:"𝗡";s:1:"N";s:4:"𝗢";s:1:"O";s:4:"𝗣";s:1:"P";s:4:"𝗤";s:1:"Q";s:4:"𝗥";s:1:"R";s:4:"𝗦";s:1:"S";s:4:"𝗧";s:1:"T";s:4:"𝗨";s:1:"U";s:4:"𝗩";s:1:"V";s:4:"𝗪";s:1:"W";s:4:"𝗫";s:1:"X";s:4:"𝗬";s:1:"Y";s:4:"𝗭";s:1:"Z";s:4:"𝗮";s:1:"a";s:4:"𝗯";s:1:"b";s:4:"𝗰";s:1:"c";s:4:"𝗱";s:1:"d";s:4:"𝗲";s:1:"e";s:4:"𝗳";s:1:"f";s:4:"𝗴";s:1:"g";s:4:"𝗵";s:1:"h";s:4:"𝗶";s:1:"i";s:4:"𝗷";s:1:"j";s:4:"𝗸";s:1:"k";s:4:"𝗹";s:1:"l";s:4:"𝗺";s:1:"m";s:4:"𝗻";s:1:"n";s:4:"𝗼";s:1:"o";s:4:"𝗽";s:1:"p";s:4:"𝗾";s:1:"q";s:4:"𝗿";s:1:"r";s:4:"𝘀";s:1:"s";s:4:"𝘁";s:1:"t";s:4:"𝘂";s:1:"u";s:4:"𝘃";s:1:"v";s:4:"𝘄";s:1:"w";s:4:"𝘅";s:1:"x";s:4:"𝘆";s:1:"y";s:4:"𝘇";s:1:"z";s:4:"𝘈";s:1:"A";s:4:"𝘉";s:1:"B";s:4:"𝘊";s:1:"C";s:4:"𝘋";s:1:"D";s:4:"𝘌";s:1:"E";s:4:"𝘍";s:1:"F";s:4:"𝘎";s:1:"G";s:4:"𝘏";s:1:"H";s:4:"𝘐";s:1:"I";s:4:"𝘑";s:1:"J";s:4:"𝘒";s:1:"K";s:4:"𝘓";s:1:"L";s:4:"𝘔";s:1:"M";s:4:"𝘕";s:1:"N";s:4:"𝘖";s:1:"O";s:4:"𝘗";s:1:"P";s:4:"𝘘";s:1:"Q";s:4:"𝘙";s:1:"R";s:4:"𝘚";s:1:"S";s:4:"𝘛";s:1:"T";s:4:"𝘜";s:1:"U";s:4:"𝘝";s:1:"V";s:4:"𝘞";s:1:"W";s:4:"𝘟";s:1:"X";s:4:"𝘠";s:1:"Y";s:4:"𝘡";s:1:"Z";s:4:"𝘢";s:1:"a";s:4:"𝘣";s:1:"b";s:4:"𝘤";s:1:"c";s:4:"𝘥";s:1:"d";s:4:"𝘦";s:1:"e";s:4:"𝘧";s:1:"f";s:4:"𝘨";s:1:"g";s:4:"𝘩";s:1:"h";s:4:"𝘪";s:1:"i";s:4:"𝘫";s:1:"j";s:4:"𝘬";s:1:"k";s:4:"𝘭";s:1:"l";s:4:"𝘮";s:1:"m";s:4:"𝘯";s:1:"n";s:4:"𝘰";s:1:"o";s:4:"𝘱";s:1:"p";s:4:"𝘲";s:1:"q";s:4:"𝘳";s:1:"r";s:4:"𝘴";s:1:"s";s:4:"𝘵";s:1:"t";s:4:"𝘶";s:1:"u";s:4:"𝘷";s:1:"v";s:4:"𝘸";s:1:"w";s:4:"𝘹";s:1:"x";s:4:"𝘺";s:1:"y";s:4:"𝘻";s:1:"z";s:4:"𝘼";s:1:"A";s:4:"𝘽";s:1:"B";s:4:"𝘾";s:1:"C";s:4:"𝘿";s:1:"D";s:4:"𝙀";s:1:"E";s:4:"𝙁";s:1:"F";s:4:"𝙂";s:1:"G";s:4:"𝙃";s:1:"H";s:4:"𝙄";s:1:"I";s:4:"𝙅";s:1:"J";s:4:"𝙆";s:1:"K";s:4:"𝙇";s:1:"L";s:4:"𝙈";s:1:"M";s:4:"𝙉";s:1:"N";s:4:"𝙊";s:1:"O";s:4:"𝙋";s:1:"P";s:4:"𝙌";s:1:"Q";s:4:"𝙍";s:1:"R";s:4:"𝙎";s:1:"S";s:4:"𝙏";s:1:"T";s:4:"𝙐";s:1:"U";s:4:"𝙑";s:1:"V";s:4:"𝙒";s:1:"W";s:4:"𝙓";s:1:"X";s:4:"𝙔";s:1:"Y";s:4:"𝙕";s:1:"Z";s:4:"𝙖";s:1:"a";s:4:"𝙗";s:1:"b";s:4:"𝙘";s:1:"c";s:4:"𝙙";s:1:"d";s:4:"𝙚";s:1:"e";s:4:"𝙛";s:1:"f";s:4:"𝙜";s:1:"g";s:4:"𝙝";s:1:"h";s:4:"𝙞";s:1:"i";s:4:"𝙟";s:1:"j";s:4:"𝙠";s:1:"k";s:4:"𝙡";s:1:"l";s:4:"𝙢";s:1:"m";s:4:"𝙣";s:1:"n";s:4:"𝙤";s:1:"o";s:4:"𝙥";s:1:"p";s:4:"𝙦";s:1:"q";s:4:"𝙧";s:1:"r";s:4:"𝙨";s:1:"s";s:4:"𝙩";s:1:"t";s:4:"𝙪";s:1:"u";s:4:"𝙫";s:1:"v";s:4:"𝙬";s:1:"w";s:4:"𝙭";s:1:"x";s:4:"𝙮";s:1:"y";s:4:"𝙯";s:1:"z";s:4:"𝙰";s:1:"A";s:4:"𝙱";s:1:"B";s:4:"𝙲";s:1:"C";s:4:"𝙳";s:1:"D";s:4:"𝙴";s:1:"E";s:4:"𝙵";s:1:"F";s:4:"𝙶";s:1:"G";s:4:"𝙷";s:1:"H";s:4:"𝙸";s:1:"I";s:4:"𝙹";s:1:"J";s:4:"𝙺";s:1:"K";s:4:"𝙻";s:1:"L";s:4:"𝙼";s:1:"M";s:4:"𝙽";s:1:"N";s:4:"𝙾";s:1:"O";s:4:"𝙿";s:1:"P";s:4:"𝚀";s:1:"Q";s:4:"𝚁";s:1:"R";s:4:"𝚂";s:1:"S";s:4:"𝚃";s:1:"T";s:4:"𝚄";s:1:"U";s:4:"𝚅";s:1:"V";s:4:"𝚆";s:1:"W";s:4:"𝚇";s:1:"X";s:4:"𝚈";s:1:"Y";s:4:"𝚉";s:1:"Z";s:4:"𝚊";s:1:"a";s:4:"𝚋";s:1:"b";s:4:"𝚌";s:1:"c";s:4:"𝚍";s:1:"d";s:4:"𝚎";s:1:"e";s:4:"𝚏";s:1:"f";s:4:"𝚐";s:1:"g";s:4:"𝚑";s:1:"h";s:4:"𝚒";s:1:"i";s:4:"𝚓";s:1:"j";s:4:"𝚔";s:1:"k";s:4:"𝚕";s:1:"l";s:4:"𝚖";s:1:"m";s:4:"𝚗";s:1:"n";s:4:"𝚘";s:1:"o";s:4:"𝚙";s:1:"p";s:4:"𝚚";s:1:"q";s:4:"𝚛";s:1:"r";s:4:"𝚜";s:1:"s";s:4:"𝚝";s:1:"t";s:4:"𝚞";s:1:"u";s:4:"𝚟";s:1:"v";s:4:"𝚠";s:1:"w";s:4:"𝚡";s:1:"x";s:4:"𝚢";s:1:"y";s:4:"𝚣";s:1:"z";s:4:"𝚤";s:2:"ı";s:4:"𝚥";s:2:"ȷ";s:4:"𝚨";s:2:"Α";s:4:"𝚩";s:2:"Β";s:4:"𝚪";s:2:"Γ";s:4:"𝚫";s:2:"Δ";s:4:"𝚬";s:2:"Ε";s:4:"𝚭";s:2:"Ζ";s:4:"𝚮";s:2:"Η";s:4:"𝚯";s:2:"Θ";s:4:"𝚰";s:2:"Ι";s:4:"𝚱";s:2:"Κ";s:4:"𝚲";s:2:"Λ";s:4:"𝚳";s:2:"Μ";s:4:"𝚴";s:2:"Ν";s:4:"𝚵";s:2:"Ξ";s:4:"𝚶";s:2:"Ο";s:4:"𝚷";s:2:"Π";s:4:"𝚸";s:2:"Ρ";s:4:"𝚹";s:2:"Θ";s:4:"𝚺";s:2:"Σ";s:4:"𝚻";s:2:"Τ";s:4:"𝚼";s:2:"Υ";s:4:"𝚽";s:2:"Φ";s:4:"𝚾";s:2:"Χ";s:4:"𝚿";s:2:"Ψ";s:4:"𝛀";s:2:"Ω";s:4:"𝛁";s:3:"∇";s:4:"𝛂";s:2:"α";s:4:"𝛃";s:2:"β";s:4:"𝛄";s:2:"γ";s:4:"𝛅";s:2:"δ";s:4:"𝛆";s:2:"ε";s:4:"𝛇";s:2:"ζ";s:4:"𝛈";s:2:"η";s:4:"𝛉";s:2:"θ";s:4:"𝛊";s:2:"ι";s:4:"𝛋";s:2:"κ";s:4:"𝛌";s:2:"λ";s:4:"𝛍";s:2:"μ";s:4:"𝛎";s:2:"ν";s:4:"𝛏";s:2:"ξ";s:4:"𝛐";s:2:"ο";s:4:"𝛑";s:2:"π";s:4:"𝛒";s:2:"ρ";s:4:"𝛓";s:2:"ς";s:4:"𝛔";s:2:"σ";s:4:"𝛕";s:2:"τ";s:4:"𝛖";s:2:"υ";s:4:"𝛗";s:2:"φ";s:4:"𝛘";s:2:"χ";s:4:"𝛙";s:2:"ψ";s:4:"𝛚";s:2:"ω";s:4:"𝛛";s:3:"∂";s:4:"𝛜";s:2:"ε";s:4:"𝛝";s:2:"θ";s:4:"𝛞";s:2:"κ";s:4:"𝛟";s:2:"φ";s:4:"𝛠";s:2:"ρ";s:4:"𝛡";s:2:"π";s:4:"𝛢";s:2:"Α";s:4:"𝛣";s:2:"Β";s:4:"𝛤";s:2:"Γ";s:4:"𝛥";s:2:"Δ";s:4:"𝛦";s:2:"Ε";s:4:"𝛧";s:2:"Ζ";s:4:"𝛨";s:2:"Η";s:4:"𝛩";s:2:"Θ";s:4:"𝛪";s:2:"Ι";s:4:"𝛫";s:2:"Κ";s:4:"𝛬";s:2:"Λ";s:4:"𝛭";s:2:"Μ";s:4:"𝛮";s:2:"Ν";s:4:"𝛯";s:2:"Ξ";s:4:"𝛰";s:2:"Ο";s:4:"𝛱";s:2:"Π";s:4:"𝛲";s:2:"Ρ";s:4:"𝛳";s:2:"Θ";s:4:"𝛴";s:2:"Σ";s:4:"𝛵";s:2:"Τ";s:4:"𝛶";s:2:"Υ";s:4:"𝛷";s:2:"Φ";s:4:"𝛸";s:2:"Χ";s:4:"𝛹";s:2:"Ψ";s:4:"𝛺";s:2:"Ω";s:4:"𝛻";s:3:"∇";s:4:"𝛼";s:2:"α";s:4:"𝛽";s:2:"β";s:4:"𝛾";s:2:"γ";s:4:"𝛿";s:2:"δ";s:4:"𝜀";s:2:"ε";s:4:"𝜁";s:2:"ζ";s:4:"𝜂";s:2:"η";s:4:"𝜃";s:2:"θ";s:4:"𝜄";s:2:"ι";s:4:"𝜅";s:2:"κ";s:4:"𝜆";s:2:"λ";s:4:"𝜇";s:2:"μ";s:4:"𝜈";s:2:"ν";s:4:"𝜉";s:2:"ξ";s:4:"𝜊";s:2:"ο";s:4:"𝜋";s:2:"π";s:4:"𝜌";s:2:"ρ";s:4:"𝜍";s:2:"ς";s:4:"𝜎";s:2:"σ";s:4:"𝜏";s:2:"τ";s:4:"𝜐";s:2:"υ";s:4:"𝜑";s:2:"φ";s:4:"𝜒";s:2:"χ";s:4:"𝜓";s:2:"ψ";s:4:"𝜔";s:2:"ω";s:4:"𝜕";s:3:"∂";s:4:"𝜖";s:2:"ε";s:4:"𝜗";s:2:"θ";s:4:"𝜘";s:2:"κ";s:4:"𝜙";s:2:"φ";s:4:"𝜚";s:2:"ρ";s:4:"𝜛";s:2:"π";s:4:"𝜜";s:2:"Α";s:4:"𝜝";s:2:"Β";s:4:"𝜞";s:2:"Γ";s:4:"𝜟";s:2:"Δ";s:4:"𝜠";s:2:"Ε";s:4:"𝜡";s:2:"Ζ";s:4:"𝜢";s:2:"Η";s:4:"𝜣";s:2:"Θ";s:4:"𝜤";s:2:"Ι";s:4:"𝜥";s:2:"Κ";s:4:"𝜦";s:2:"Λ";s:4:"𝜧";s:2:"Μ";s:4:"𝜨";s:2:"Ν";s:4:"𝜩";s:2:"Ξ";s:4:"𝜪";s:2:"Ο";s:4:"𝜫";s:2:"Π";s:4:"𝜬";s:2:"Ρ";s:4:"𝜭";s:2:"Θ";s:4:"𝜮";s:2:"Σ";s:4:"𝜯";s:2:"Τ";s:4:"𝜰";s:2:"Υ";s:4:"𝜱";s:2:"Φ";s:4:"𝜲";s:2:"Χ";s:4:"𝜳";s:2:"Ψ";s:4:"𝜴";s:2:"Ω";s:4:"𝜵";s:3:"∇";s:4:"𝜶";s:2:"α";s:4:"𝜷";s:2:"β";s:4:"𝜸";s:2:"γ";s:4:"𝜹";s:2:"δ";s:4:"𝜺";s:2:"ε";s:4:"𝜻";s:2:"ζ";s:4:"𝜼";s:2:"η";s:4:"𝜽";s:2:"θ";s:4:"𝜾";s:2:"ι";s:4:"𝜿";s:2:"κ";s:4:"𝝀";s:2:"λ";s:4:"𝝁";s:2:"μ";s:4:"𝝂";s:2:"ν";s:4:"𝝃";s:2:"ξ";s:4:"𝝄";s:2:"ο";s:4:"𝝅";s:2:"π";s:4:"𝝆";s:2:"ρ";s:4:"𝝇";s:2:"ς";s:4:"𝝈";s:2:"σ";s:4:"𝝉";s:2:"τ";s:4:"𝝊";s:2:"υ";s:4:"𝝋";s:2:"φ";s:4:"𝝌";s:2:"χ";s:4:"𝝍";s:2:"ψ";s:4:"𝝎";s:2:"ω";s:4:"𝝏";s:3:"∂";s:4:"𝝐";s:2:"ε";s:4:"𝝑";s:2:"θ";s:4:"𝝒";s:2:"κ";s:4:"𝝓";s:2:"φ";s:4:"𝝔";s:2:"ρ";s:4:"𝝕";s:2:"π";s:4:"𝝖";s:2:"Α";s:4:"𝝗";s:2:"Β";s:4:"𝝘";s:2:"Γ";s:4:"𝝙";s:2:"Δ";s:4:"𝝚";s:2:"Ε";s:4:"𝝛";s:2:"Ζ";s:4:"𝝜";s:2:"Η";s:4:"𝝝";s:2:"Θ";s:4:"𝝞";s:2:"Ι";s:4:"𝝟";s:2:"Κ";s:4:"𝝠";s:2:"Λ";s:4:"𝝡";s:2:"Μ";s:4:"𝝢";s:2:"Ν";s:4:"𝝣";s:2:"Ξ";s:4:"𝝤";s:2:"Ο";s:4:"𝝥";s:2:"Π";s:4:"𝝦";s:2:"Ρ";s:4:"𝝧";s:2:"Θ";s:4:"𝝨";s:2:"Σ";s:4:"𝝩";s:2:"Τ";s:4:"𝝪";s:2:"Υ";s:4:"𝝫";s:2:"Φ";s:4:"𝝬";s:2:"Χ";s:4:"𝝭";s:2:"Ψ";s:4:"𝝮";s:2:"Ω";s:4:"𝝯";s:3:"∇";s:4:"𝝰";s:2:"α";s:4:"𝝱";s:2:"β";s:4:"𝝲";s:2:"γ";s:4:"𝝳";s:2:"δ";s:4:"𝝴";s:2:"ε";s:4:"𝝵";s:2:"ζ";s:4:"𝝶";s:2:"η";s:4:"𝝷";s:2:"θ";s:4:"𝝸";s:2:"ι";s:4:"𝝹";s:2:"κ";s:4:"𝝺";s:2:"λ";s:4:"𝝻";s:2:"μ";s:4:"𝝼";s:2:"ν";s:4:"𝝽";s:2:"ξ";s:4:"𝝾";s:2:"ο";s:4:"𝝿";s:2:"π";s:4:"𝞀";s:2:"ρ";s:4:"𝞁";s:2:"ς";s:4:"𝞂";s:2:"σ";s:4:"𝞃";s:2:"τ";s:4:"𝞄";s:2:"υ";s:4:"𝞅";s:2:"φ";s:4:"𝞆";s:2:"χ";s:4:"𝞇";s:2:"ψ";s:4:"𝞈";s:2:"ω";s:4:"𝞉";s:3:"∂";s:4:"𝞊";s:2:"ε";s:4:"𝞋";s:2:"θ";s:4:"𝞌";s:2:"κ";s:4:"𝞍";s:2:"φ";s:4:"𝞎";s:2:"ρ";s:4:"𝞏";s:2:"π";s:4:"𝞐";s:2:"Α";s:4:"𝞑";s:2:"Β";s:4:"𝞒";s:2:"Γ";s:4:"𝞓";s:2:"Δ";s:4:"𝞔";s:2:"Ε";s:4:"𝞕";s:2:"Ζ";s:4:"𝞖";s:2:"Η";s:4:"𝞗";s:2:"Θ";s:4:"𝞘";s:2:"Ι";s:4:"𝞙";s:2:"Κ";s:4:"𝞚";s:2:"Λ";s:4:"𝞛";s:2:"Μ";s:4:"𝞜";s:2:"Ν";s:4:"𝞝";s:2:"Ξ";s:4:"𝞞";s:2:"Ο";s:4:"𝞟";s:2:"Π";s:4:"𝞠";s:2:"Ρ";s:4:"𝞡";s:2:"Θ";s:4:"𝞢";s:2:"Σ";s:4:"𝞣";s:2:"Τ";s:4:"𝞤";s:2:"Υ";s:4:"𝞥";s:2:"Φ";s:4:"𝞦";s:2:"Χ";s:4:"𝞧";s:2:"Ψ";s:4:"𝞨";s:2:"Ω";s:4:"𝞩";s:3:"∇";s:4:"𝞪";s:2:"α";s:4:"𝞫";s:2:"β";s:4:"𝞬";s:2:"γ";s:4:"𝞭";s:2:"δ";s:4:"𝞮";s:2:"ε";s:4:"𝞯";s:2:"ζ";s:4:"𝞰";s:2:"η";s:4:"𝞱";s:2:"θ";s:4:"𝞲";s:2:"ι";s:4:"𝞳";s:2:"κ";s:4:"𝞴";s:2:"λ";s:4:"𝞵";s:2:"μ";s:4:"𝞶";s:2:"ν";s:4:"𝞷";s:2:"ξ";s:4:"𝞸";s:2:"ο";s:4:"𝞹";s:2:"π";s:4:"𝞺";s:2:"ρ";s:4:"𝞻";s:2:"ς";s:4:"𝞼";s:2:"σ";s:4:"𝞽";s:2:"τ";s:4:"𝞾";s:2:"υ";s:4:"𝞿";s:2:"φ";s:4:"𝟀";s:2:"χ";s:4:"𝟁";s:2:"ψ";s:4:"𝟂";s:2:"ω";s:4:"𝟃";s:3:"∂";s:4:"𝟄";s:2:"ε";s:4:"𝟅";s:2:"θ";s:4:"𝟆";s:2:"κ";s:4:"𝟇";s:2:"φ";s:4:"𝟈";s:2:"ρ";s:4:"𝟉";s:2:"π";s:4:"𝟊";s:2:"Ϝ";s:4:"𝟋";s:2:"ϝ";s:4:"𝟎";s:1:"0";s:4:"𝟏";s:1:"1";s:4:"𝟐";s:1:"2";s:4:"𝟑";s:1:"3";s:4:"𝟒";s:1:"4";s:4:"𝟓";s:1:"5";s:4:"𝟔";s:1:"6";s:4:"𝟕";s:1:"7";s:4:"𝟖";s:1:"8";s:4:"𝟗";s:1:"9";s:4:"𝟘";s:1:"0";s:4:"𝟙";s:1:"1";s:4:"𝟚";s:1:"2";s:4:"𝟛";s:1:"3";s:4:"𝟜";s:1:"4";s:4:"𝟝";s:1:"5";s:4:"𝟞";s:1:"6";s:4:"𝟟";s:1:"7";s:4:"𝟠";s:1:"8";s:4:"𝟡";s:1:"9";s:4:"𝟢";s:1:"0";s:4:"𝟣";s:1:"1";s:4:"𝟤";s:1:"2";s:4:"𝟥";s:1:"3";s:4:"𝟦";s:1:"4";s:4:"𝟧";s:1:"5";s:4:"𝟨";s:1:"6";s:4:"𝟩";s:1:"7";s:4:"𝟪";s:1:"8";s:4:"𝟫";s:1:"9";s:4:"𝟬";s:1:"0";s:4:"𝟭";s:1:"1";s:4:"𝟮";s:1:"2";s:4:"𝟯";s:1:"3";s:4:"𝟰";s:1:"4";s:4:"𝟱";s:1:"5";s:4:"𝟲";s:1:"6";s:4:"𝟳";s:1:"7";s:4:"𝟴";s:1:"8";s:4:"𝟵";s:1:"9";s:4:"𝟶";s:1:"0";s:4:"𝟷";s:1:"1";s:4:"𝟸";s:1:"2";s:4:"𝟹";s:1:"3";s:4:"𝟺";s:1:"4";s:4:"𝟻";s:1:"5";s:4:"𝟼";s:1:"6";s:4:"𝟽";s:1:"7";s:4:"𝟾";s:1:"8";s:4:"𝟿";s:1:"9";s:4:"🄀";s:2:"0.";s:4:"🄁";s:2:"0,";s:4:"🄂";s:2:"1,";s:4:"🄃";s:2:"2,";s:4:"🄄";s:2:"3,";s:4:"🄅";s:2:"4,";s:4:"🄆";s:2:"5,";s:4:"🄇";s:2:"6,";s:4:"🄈";s:2:"7,";s:4:"🄉";s:2:"8,";s:4:"🄊";s:2:"9,";s:4:"🄐";s:3:"(A)";s:4:"🄑";s:3:"(B)";s:4:"🄒";s:3:"(C)";s:4:"🄓";s:3:"(D)";s:4:"🄔";s:3:"(E)";s:4:"🄕";s:3:"(F)";s:4:"🄖";s:3:"(G)";s:4:"🄗";s:3:"(H)";s:4:"🄘";s:3:"(I)";s:4:"🄙";s:3:"(J)";s:4:"🄚";s:3:"(K)";s:4:"🄛";s:3:"(L)";s:4:"🄜";s:3:"(M)";s:4:"🄝";s:3:"(N)";s:4:"🄞";s:3:"(O)";s:4:"🄟";s:3:"(P)";s:4:"🄠";s:3:"(Q)";s:4:"🄡";s:3:"(R)";s:4:"🄢";s:3:"(S)";s:4:"🄣";s:3:"(T)";s:4:"🄤";s:3:"(U)";s:4:"🄥";s:3:"(V)";s:4:"🄦";s:3:"(W)";s:4:"🄧";s:3:"(X)";s:4:"🄨";s:3:"(Y)";s:4:"🄩";s:3:"(Z)";s:4:"🄪";s:7:"〔S〕";s:4:"🄫";s:1:"C";s:4:"🄬";s:1:"R";s:4:"🄭";s:2:"CD";s:4:"🄮";s:2:"WZ";s:4:"🄰";s:1:"A";s:4:"🄱";s:1:"B";s:4:"🄲";s:1:"C";s:4:"🄳";s:1:"D";s:4:"🄴";s:1:"E";s:4:"🄵";s:1:"F";s:4:"🄶";s:1:"G";s:4:"🄷";s:1:"H";s:4:"🄸";s:1:"I";s:4:"🄹";s:1:"J";s:4:"🄺";s:1:"K";s:4:"🄻";s:1:"L";s:4:"🄼";s:1:"M";s:4:"🄽";s:1:"N";s:4:"🄾";s:1:"O";s:4:"🄿";s:1:"P";s:4:"🅀";s:1:"Q";s:4:"🅁";s:1:"R";s:4:"🅂";s:1:"S";s:4:"🅃";s:1:"T";s:4:"🅄";s:1:"U";s:4:"🅅";s:1:"V";s:4:"🅆";s:1:"W";s:4:"🅇";s:1:"X";s:4:"🅈";s:1:"Y";s:4:"🅉";s:1:"Z";s:4:"🅊";s:2:"HV";s:4:"🅋";s:2:"MV";s:4:"🅌";s:2:"SD";s:4:"🅍";s:2:"SS";s:4:"🅎";s:3:"PPV";s:4:"🅏";s:2:"WC";s:4:"🆐";s:2:"DJ";s:4:"🈀";s:6:"ほか";s:4:"🈁";s:6:"ココ";s:4:"🈂";s:3:"サ";s:4:"🈐";s:3:"手";s:4:"🈑";s:3:"字";s:4:"🈒";s:3:"双";s:4:"🈓";s:6:"デ";s:4:"🈔";s:3:"二";s:4:"🈕";s:3:"多";s:4:"🈖";s:3:"解";s:4:"🈗";s:3:"天";s:4:"🈘";s:3:"交";s:4:"🈙";s:3:"映";s:4:"🈚";s:3:"無";s:4:"🈛";s:3:"料";s:4:"🈜";s:3:"前";s:4:"🈝";s:3:"後";s:4:"🈞";s:3:"再";s:4:"🈟";s:3:"新";s:4:"🈠";s:3:"初";s:4:"🈡";s:3:"終";s:4:"🈢";s:3:"生";s:4:"🈣";s:3:"販";s:4:"🈤";s:3:"声";s:4:"🈥";s:3:"吹";s:4:"🈦";s:3:"演";s:4:"🈧";s:3:"投";s:4:"🈨";s:3:"捕";s:4:"🈩";s:3:"一";s:4:"🈪";s:3:"三";s:4:"🈫";s:3:"遊";s:4:"🈬";s:3:"左";s:4:"🈭";s:3:"中";s:4:"🈮";s:3:"右";s:4:"🈯";s:3:"指";s:4:"🈰";s:3:"走";s:4:"🈱";s:3:"打";s:4:"🈲";s:3:"禁";s:4:"🈳";s:3:"空";s:4:"🈴";s:3:"合";s:4:"🈵";s:3:"満";s:4:"🈶";s:3:"有";s:4:"🈷";s:3:"月";s:4:"🈸";s:3:"申";s:4:"🈹";s:3:"割";s:4:"🈺";s:3:"営";s:4:"🉀";s:9:"〔本〕";s:4:"🉁";s:9:"〔三〕";s:4:"🉂";s:9:"〔二〕";s:4:"🉃";s:9:"〔安〕";s:4:"🉄";s:9:"〔点〕";s:4:"🉅";s:9:"〔打〕";s:4:"🉆";s:9:"〔盗〕";s:4:"🉇";s:9:"〔勝〕";s:4:"🉈";s:9:"〔敗〕";s:4:"🉐";s:3:"得";s:4:"🉑";s:3:"可";s:4:"丽";s:3:"丽";s:4:"丸";s:3:"丸";s:4:"乁";s:3:"乁";s:4:"𠄢";s:4:"𠄢";s:4:"你";s:3:"你";s:4:"侮";s:3:"侮";s:4:"侻";s:3:"侻";s:4:"倂";s:3:"倂";s:4:"偺";s:3:"偺";s:4:"備";s:3:"備";s:4:"僧";s:3:"僧";s:4:"像";s:3:"像";s:4:"㒞";s:3:"㒞";s:4:"𠘺";s:4:"𠘺";s:4:"免";s:3:"免";s:4:"兔";s:3:"兔";s:4:"兤";s:3:"兤";s:4:"具";s:3:"具";s:4:"𠔜";s:4:"𠔜";s:4:"㒹";s:3:"㒹";s:4:"內";s:3:"內";s:4:"再";s:3:"再";s:4:"𠕋";s:4:"𠕋";s:4:"冗";s:3:"冗";s:4:"冤";s:3:"冤";s:4:"仌";s:3:"仌";s:4:"冬";s:3:"冬";s:4:"况";s:3:"况";s:4:"𩇟";s:4:"𩇟";s:4:"凵";s:3:"凵";s:4:"刃";s:3:"刃";s:4:"㓟";s:3:"㓟";s:4:"刻";s:3:"刻";s:4:"剆";s:3:"剆";s:4:"割";s:3:"割";s:4:"剷";s:3:"剷";s:4:"㔕";s:3:"㔕";s:4:"勇";s:3:"勇";s:4:"勉";s:3:"勉";s:4:"勤";s:3:"勤";s:4:"勺";s:3:"勺";s:4:"包";s:3:"包";s:4:"匆";s:3:"匆";s:4:"北";s:3:"北";s:4:"卉";s:3:"卉";s:4:"卑";s:3:"卑";s:4:"博";s:3:"博";s:4:"即";s:3:"即";s:4:"卽";s:3:"卽";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"卿";s:3:"卿";s:4:"𠨬";s:4:"𠨬";s:4:"灰";s:3:"灰";s:4:"及";s:3:"及";s:4:"叟";s:3:"叟";s:4:"𠭣";s:4:"𠭣";s:4:"叫";s:3:"叫";s:4:"叱";s:3:"叱";s:4:"吆";s:3:"吆";s:4:"咞";s:3:"咞";s:4:"吸";s:3:"吸";s:4:"呈";s:3:"呈";s:4:"周";s:3:"周";s:4:"咢";s:3:"咢";s:4:"哶";s:3:"哶";s:4:"唐";s:3:"唐";s:4:"啓";s:3:"啓";s:4:"啣";s:3:"啣";s:4:"善";s:3:"善";s:4:"善";s:3:"善";s:4:"喙";s:3:"喙";s:4:"喫";s:3:"喫";s:4:"喳";s:3:"喳";s:4:"嗂";s:3:"嗂";s:4:"圖";s:3:"圖";s:4:"嘆";s:3:"嘆";s:4:"圗";s:3:"圗";s:4:"噑";s:3:"噑";s:4:"噴";s:3:"噴";s:4:"切";s:3:"切";s:4:"壮";s:3:"壮";s:4:"城";s:3:"城";s:4:"埴";s:3:"埴";s:4:"堍";s:3:"堍";s:4:"型";s:3:"型";s:4:"堲";s:3:"堲";s:4:"報";s:3:"報";s:4:"墬";s:3:"墬";s:4:"𡓤";s:4:"𡓤";s:4:"売";s:3:"売";s:4:"壷";s:3:"壷";s:4:"夆";s:3:"夆";s:4:"多";s:3:"多";s:4:"夢";s:3:"夢";s:4:"奢";s:3:"奢";s:4:"𡚨";s:4:"𡚨";s:4:"𡛪";s:4:"𡛪";s:4:"姬";s:3:"姬";s:4:"娛";s:3:"娛";s:4:"娧";s:3:"娧";s:4:"姘";s:3:"姘";s:4:"婦";s:3:"婦";s:4:"㛮";s:3:"㛮";s:4:"㛼";s:3:"㛼";s:4:"嬈";s:3:"嬈";s:4:"嬾";s:3:"嬾";s:4:"嬾";s:3:"嬾";s:4:"𡧈";s:4:"𡧈";s:4:"寃";s:3:"寃";s:4:"寘";s:3:"寘";s:4:"寧";s:3:"寧";s:4:"寳";s:3:"寳";s:4:"𡬘";s:4:"𡬘";s:4:"寿";s:3:"寿";s:4:"将";s:3:"将";s:4:"当";s:3:"当";s:4:"尢";s:3:"尢";s:4:"㞁";s:3:"㞁";s:4:"屠";s:3:"屠";s:4:"屮";s:3:"屮";s:4:"峀";s:3:"峀";s:4:"岍";s:3:"岍";s:4:"𡷤";s:4:"𡷤";s:4:"嵃";s:3:"嵃";s:4:"𡷦";s:4:"𡷦";s:4:"嵮";s:3:"嵮";s:4:"嵫";s:3:"嵫";s:4:"嵼";s:3:"嵼";s:4:"巡";s:3:"巡";s:4:"巢";s:3:"巢";s:4:"㠯";s:3:"㠯";s:4:"巽";s:3:"巽";s:4:"帨";s:3:"帨";s:4:"帽";s:3:"帽";s:4:"幩";s:3:"幩";s:4:"㡢";s:3:"㡢";s:4:"𢆃";s:4:"𢆃";s:4:"㡼";s:3:"㡼";s:4:"庰";s:3:"庰";s:4:"庳";s:3:"庳";s:4:"庶";s:3:"庶";s:4:"廊";s:3:"廊";s:4:"𪎒";s:4:"𪎒";s:4:"廾";s:3:"廾";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"𢌱";s:4:"舁";s:3:"舁";s:4:"弢";s:3:"弢";s:4:"弢";s:3:"弢";s:4:"㣇";s:3:"㣇";s:4:"𣊸";s:4:"𣊸";s:4:"𦇚";s:4:"𦇚";s:4:"形";s:3:"形";s:4:"彫";s:3:"彫";s:4:"㣣";s:3:"㣣";s:4:"徚";s:3:"徚";s:4:"忍";s:3:"忍";s:4:"志";s:3:"志";s:4:"忹";s:3:"忹";s:4:"悁";s:3:"悁";s:4:"㤺";s:3:"㤺";s:4:"㤜";s:3:"㤜";s:4:"悔";s:3:"悔";s:4:"𢛔";s:4:"𢛔";s:4:"惇";s:3:"惇";s:4:"慈";s:3:"慈";s:4:"慌";s:3:"慌";s:4:"慎";s:3:"慎";s:4:"慌";s:3:"慌";s:4:"慺";s:3:"慺";s:4:"憎";s:3:"憎";s:4:"憲";s:3:"憲";s:4:"憤";s:3:"憤";s:4:"憯";s:3:"憯";s:4:"懞";s:3:"懞";s:4:"懲";s:3:"懲";s:4:"懶";s:3:"懶";s:4:"成";s:3:"成";s:4:"戛";s:3:"戛";s:4:"扝";s:3:"扝";s:4:"抱";s:3:"抱";s:4:"拔";s:3:"拔";s:4:"捐";s:3:"捐";s:4:"𢬌";s:4:"𢬌";s:4:"挽";s:3:"挽";s:4:"拼";s:3:"拼";s:4:"捨";s:3:"捨";s:4:"掃";s:3:"掃";s:4:"揤";s:3:"揤";s:4:"𢯱";s:4:"𢯱";s:4:"搢";s:3:"搢";s:4:"揅";s:3:"揅";s:4:"掩";s:3:"掩";s:4:"㨮";s:3:"㨮";s:4:"摩";s:3:"摩";s:4:"摾";s:3:"摾";s:4:"撝";s:3:"撝";s:4:"摷";s:3:"摷";s:4:"㩬";s:3:"㩬";s:4:"敏";s:3:"敏";s:4:"敬";s:3:"敬";s:4:"𣀊";s:4:"𣀊";s:4:"旣";s:3:"旣";s:4:"書";s:3:"書";s:4:"晉";s:3:"晉";s:4:"㬙";s:3:"㬙";s:4:"暑";s:3:"暑";s:4:"㬈";s:3:"㬈";s:4:"㫤";s:3:"㫤";s:4:"冒";s:3:"冒";s:4:"冕";s:3:"冕";s:4:"最";s:3:"最";s:4:"暜";s:3:"暜";s:4:"肭";s:3:"肭";s:4:"䏙";s:3:"䏙";s:4:"朗";s:3:"朗";s:4:"望";s:3:"望";s:4:"朡";s:3:"朡";s:4:"杞";s:3:"杞";s:4:"杓";s:3:"杓";s:4:"𣏃";s:4:"𣏃";s:4:"㭉";s:3:"㭉";s:4:"柺";s:3:"柺";s:4:"枅";s:3:"枅";s:4:"桒";s:3:"桒";s:4:"梅";s:3:"梅";s:4:"𣑭";s:4:"𣑭";s:4:"梎";s:3:"梎";s:4:"栟";s:3:"栟";s:4:"椔";s:3:"椔";s:4:"㮝";s:3:"㮝";s:4:"楂";s:3:"楂";s:4:"榣";s:3:"榣";s:4:"槪";s:3:"槪";s:4:"檨";s:3:"檨";s:4:"𣚣";s:4:"𣚣";s:4:"櫛";s:3:"櫛";s:4:"㰘";s:3:"㰘";s:4:"次";s:3:"次";s:4:"𣢧";s:4:"𣢧";s:4:"歔";s:3:"歔";s:4:"㱎";s:3:"㱎";s:4:"歲";s:3:"歲";s:4:"殟";s:3:"殟";s:4:"殺";s:3:"殺";s:4:"殻";s:3:"殻";s:4:"𣪍";s:4:"𣪍";s:4:"𡴋";s:4:"𡴋";s:4:"𣫺";s:4:"𣫺";s:4:"汎";s:3:"汎";s:4:"𣲼";s:4:"𣲼";s:4:"沿";s:3:"沿";s:4:"泍";s:3:"泍";s:4:"汧";s:3:"汧";s:4:"洖";s:3:"洖";s:4:"派";s:3:"派";s:4:"海";s:3:"海";s:4:"流";s:3:"流";s:4:"浩";s:3:"浩";s:4:"浸";s:3:"浸";s:4:"涅";s:3:"涅";s:4:"𣴞";s:4:"𣴞";s:4:"洴";s:3:"洴";s:4:"港";s:3:"港";s:4:"湮";s:3:"湮";s:4:"㴳";s:3:"㴳";s:4:"滋";s:3:"滋";s:4:"滇";s:3:"滇";s:4:"𣻑";s:4:"𣻑";s:4:"淹";s:3:"淹";s:4:"潮";s:3:"潮";s:4:"𣽞";s:4:"𣽞";s:4:"𣾎";s:4:"𣾎";s:4:"濆";s:3:"濆";s:4:"瀹";s:3:"瀹";s:4:"瀞";s:3:"瀞";s:4:"瀛";s:3:"瀛";s:4:"㶖";s:3:"㶖";s:4:"灊";s:3:"灊";s:4:"災";s:3:"災";s:4:"灷";s:3:"灷";s:4:"炭";s:3:"炭";s:4:"𠔥";s:4:"𠔥";s:4:"煅";s:3:"煅";s:4:"𤉣";s:4:"𤉣";s:4:"熜";s:3:"熜";s:4:"𤎫";s:4:"𤎫";s:4:"爨";s:3:"爨";s:4:"爵";s:3:"爵";s:4:"牐";s:3:"牐";s:4:"𤘈";s:4:"𤘈";s:4:"犀";s:3:"犀";s:4:"犕";s:3:"犕";s:4:"𤜵";s:4:"𤜵";s:4:"𤠔";s:4:"𤠔";s:4:"獺";s:3:"獺";s:4:"王";s:3:"王";s:4:"㺬";s:3:"㺬";s:4:"玥";s:3:"玥";s:4:"㺸";s:3:"㺸";s:4:"㺸";s:3:"㺸";s:4:"瑇";s:3:"瑇";s:4:"瑜";s:3:"瑜";s:4:"瑱";s:3:"瑱";s:4:"璅";s:3:"璅";s:4:"瓊";s:3:"瓊";s:4:"㼛";s:3:"㼛";s:4:"甤";s:3:"甤";s:4:"𤰶";s:4:"𤰶";s:4:"甾";s:3:"甾";s:4:"𤲒";s:4:"𤲒";s:4:"異";s:3:"異";s:4:"𢆟";s:4:"𢆟";s:4:"瘐";s:3:"瘐";s:4:"𤾡";s:4:"𤾡";s:4:"𤾸";s:4:"𤾸";s:4:"𥁄";s:4:"𥁄";s:4:"㿼";s:3:"㿼";s:4:"䀈";s:3:"䀈";s:4:"直";s:3:"直";s:4:"𥃳";s:4:"𥃳";s:4:"𥃲";s:4:"𥃲";s:4:"𥄙";s:4:"𥄙";s:4:"𥄳";s:4:"𥄳";s:4:"眞";s:3:"眞";s:4:"真";s:3:"真";s:4:"真";s:3:"真";s:4:"睊";s:3:"睊";s:4:"䀹";s:3:"䀹";s:4:"瞋";s:3:"瞋";s:4:"䁆";s:3:"䁆";s:4:"䂖";s:3:"䂖";s:4:"𥐝";s:4:"𥐝";s:4:"硎";s:3:"硎";s:4:"碌";s:3:"碌";s:4:"磌";s:3:"磌";s:4:"䃣";s:3:"䃣";s:4:"𥘦";s:4:"𥘦";s:4:"祖";s:3:"祖";s:4:"𥚚";s:4:"𥚚";s:4:"𥛅";s:4:"𥛅";s:4:"福";s:3:"福";s:4:"秫";s:3:"秫";s:4:"䄯";s:3:"䄯";s:4:"穀";s:3:"穀";s:4:"穊";s:3:"穊";s:4:"穏";s:3:"穏";s:4:"𥥼";s:4:"𥥼";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"𥪧";s:4:"竮";s:3:"竮";s:4:"䈂";s:3:"䈂";s:4:"𥮫";s:4:"𥮫";s:4:"篆";s:3:"篆";s:4:"築";s:3:"築";s:4:"䈧";s:3:"䈧";s:4:"𥲀";s:4:"𥲀";s:4:"糒";s:3:"糒";s:4:"䊠";s:3:"䊠";s:4:"糨";s:3:"糨";s:4:"糣";s:3:"糣";s:4:"紀";s:3:"紀";s:4:"𥾆";s:4:"𥾆";s:4:"絣";s:3:"絣";s:4:"䌁";s:3:"䌁";s:4:"緇";s:3:"緇";s:4:"縂";s:3:"縂";s:4:"繅";s:3:"繅";s:4:"䌴";s:3:"䌴";s:4:"𦈨";s:4:"𦈨";s:4:"𦉇";s:4:"𦉇";s:4:"䍙";s:3:"䍙";s:4:"𦋙";s:4:"𦋙";s:4:"罺";s:3:"罺";s:4:"𦌾";s:4:"𦌾";s:4:"羕";s:3:"羕";s:4:"翺";s:3:"翺";s:4:"者";s:3:"者";s:4:"𦓚";s:4:"𦓚";s:4:"𦔣";s:4:"𦔣";s:4:"聠";s:3:"聠";s:4:"𦖨";s:4:"𦖨";s:4:"聰";s:3:"聰";s:4:"𣍟";s:4:"𣍟";s:4:"䏕";s:3:"䏕";s:4:"育";s:3:"育";s:4:"脃";s:3:"脃";s:4:"䐋";s:3:"䐋";s:4:"脾";s:3:"脾";s:4:"媵";s:3:"媵";s:4:"𦞧";s:4:"𦞧";s:4:"𦞵";s:4:"𦞵";s:4:"𣎓";s:4:"𣎓";s:4:"𣎜";s:4:"𣎜";s:4:"舁";s:3:"舁";s:4:"舄";s:3:"舄";s:4:"辞";s:3:"辞";s:4:"䑫";s:3:"䑫";s:4:"芑";s:3:"芑";s:4:"芋";s:3:"芋";s:4:"芝";s:3:"芝";s:4:"劳";s:3:"劳";s:4:"花";s:3:"花";s:4:"芳";s:3:"芳";s:4:"芽";s:3:"芽";s:4:"苦";s:3:"苦";s:4:"𦬼";s:4:"𦬼";s:4:"若";s:3:"若";s:4:"茝";s:3:"茝";s:4:"荣";s:3:"荣";s:4:"莭";s:3:"莭";s:4:"茣";s:3:"茣";s:4:"莽";s:3:"莽";s:4:"菧";s:3:"菧";s:4:"著";s:3:"著";s:4:"荓";s:3:"荓";s:4:"菊";s:3:"菊";s:4:"菌";s:3:"菌";s:4:"菜";s:3:"菜";s:4:"𦰶";s:4:"𦰶";s:4:"𦵫";s:4:"𦵫";s:4:"𦳕";s:4:"𦳕";s:4:"䔫";s:3:"䔫";s:4:"蓱";s:3:"蓱";s:4:"蓳";s:3:"蓳";s:4:"蔖";s:3:"蔖";s:4:"𧏊";s:4:"𧏊";s:4:"蕤";s:3:"蕤";s:4:"𦼬";s:4:"𦼬";s:4:"䕝";s:3:"䕝";s:4:"䕡";s:3:"䕡";s:4:"𦾱";s:4:"𦾱";s:4:"𧃒";s:4:"𧃒";s:4:"䕫";s:3:"䕫";s:4:"虐";s:3:"虐";s:4:"虜";s:3:"虜";s:4:"虧";s:3:"虧";s:4:"虩";s:3:"虩";s:4:"蚩";s:3:"蚩";s:4:"蚈";s:3:"蚈";s:4:"蜎";s:3:"蜎";s:4:"蛢";s:3:"蛢";s:4:"蝹";s:3:"蝹";s:4:"蜨";s:3:"蜨";s:4:"蝫";s:3:"蝫";s:4:"螆";s:3:"螆";s:4:"䗗";s:3:"䗗";s:4:"蟡";s:3:"蟡";s:4:"蠁";s:3:"蠁";s:4:"䗹";s:3:"䗹";s:4:"衠";s:3:"衠";s:4:"衣";s:3:"衣";s:4:"𧙧";s:4:"𧙧";s:4:"裗";s:3:"裗";s:4:"裞";s:3:"裞";s:4:"䘵";s:3:"䘵";s:4:"裺";s:3:"裺";s:4:"㒻";s:3:"㒻";s:4:"𧢮";s:4:"𧢮";s:4:"𧥦";s:4:"𧥦";s:4:"䚾";s:3:"䚾";s:4:"䛇";s:3:"䛇";s:4:"誠";s:3:"誠";s:4:"諭";s:3:"諭";s:4:"變";s:3:"變";s:4:"豕";s:3:"豕";s:4:"𧲨";s:4:"𧲨";s:4:"貫";s:3:"貫";s:4:"賁";s:3:"賁";s:4:"贛";s:3:"贛";s:4:"起";s:3:"起";s:4:"𧼯";s:4:"𧼯";s:4:"𠠄";s:4:"𠠄";s:4:"跋";s:3:"跋";s:4:"趼";s:3:"趼";s:4:"跰";s:3:"跰";s:4:"𠣞";s:4:"𠣞";s:4:"軔";s:3:"軔";s:4:"輸";s:3:"輸";s:4:"𨗒";s:4:"𨗒";s:4:"𨗭";s:4:"𨗭";s:4:"邔";s:3:"邔";s:4:"郱";s:3:"郱";s:4:"鄑";s:3:"鄑";s:4:"𨜮";s:4:"𨜮";s:4:"鄛";s:3:"鄛";s:4:"鈸";s:3:"鈸";s:4:"鋗";s:3:"鋗";s:4:"鋘";s:3:"鋘";s:4:"鉼";s:3:"鉼";s:4:"鏹";s:3:"鏹";s:4:"鐕";s:3:"鐕";s:4:"𨯺";s:4:"𨯺";s:4:"開";s:3:"開";s:4:"䦕";s:3:"䦕";s:4:"閷";s:3:"閷";s:4:"𨵷";s:4:"𨵷";s:4:"䧦";s:3:"䧦";s:4:"雃";s:3:"雃";s:4:"嶲";s:3:"嶲";s:4:"霣";s:3:"霣";s:4:"𩅅";s:4:"𩅅";s:4:"𩈚";s:4:"𩈚";s:4:"䩮";s:3:"䩮";s:4:"䩶";s:3:"䩶";s:4:"韠";s:3:"韠";s:4:"𩐊";s:4:"𩐊";s:4:"䪲";s:3:"䪲";s:4:"𩒖";s:4:"𩒖";s:4:"頋";s:3:"頋";s:4:"頋";s:3:"頋";s:4:"頩";s:3:"頩";s:4:"𩖶";s:4:"𩖶";s:4:"飢";s:3:"飢";s:4:"䬳";s:3:"䬳";s:4:"餩";s:3:"餩";s:4:"馧";s:3:"馧";s:4:"駂";s:3:"駂";s:4:"駾";s:3:"駾";s:4:"䯎";s:3:"䯎";s:4:"𩬰";s:4:"𩬰";s:4:"鬒";s:3:"鬒";s:4:"鱀";s:3:"鱀";s:4:"鳽";s:3:"鳽";s:4:"䳎";s:3:"䳎";s:4:"䳭";s:3:"䳭";s:4:"鵧";s:3:"鵧";s:4:"𪃎";s:4:"𪃎";s:4:"䳸";s:3:"䳸";s:4:"𪄅";s:4:"𪄅";s:4:"𪈎";s:4:"𪈎";s:4:"𪊑";s:4:"𪊑";s:4:"麻";s:3:"麻";s:4:"䵖";s:3:"䵖";s:4:"黹";s:3:"黹";s:4:"黾";s:3:"黾";s:4:"鼅";s:3:"鼅";s:4:"鼏";s:3:"鼏";s:4:"鼖";s:3:"鼖";s:4:"鼻";s:3:"鼻";s:4:"𪘀";s:4:"𪘀";}' );
+
diff --git a/vendor/wikimedia/utfnormal/src/Util.php b/vendor/wikimedia/utfnormal/src/Util.php
new file mode 100644
index 00000000..e5049097
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/src/Util.php
@@ -0,0 +1,157 @@
+<?php
+namespace UtfNormal;
+
+use InvalidArgumentException;
+
+/**
+ * Some of these functions are adapted from places in MediaWiki.
+ * Should probably merge them for consistency.
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal
+ */
+
+class Utils {
+ /**
+ * Return UTF-8 sequence for a given Unicode code point.
+ *
+ * @param $codepoint Integer:
+ * @return String
+ * @throws InvalidArgumentException if fed out of range data.
+ */
+ public static function codepointToUtf8 ( $codepoint ) {
+ if ( $codepoint < 0x80 ) {
+ return chr( $codepoint );
+ }
+
+ if ( $codepoint < 0x800 ) {
+ return chr( $codepoint >> 6 & 0x3f | 0xc0 ) .
+ chr( $codepoint & 0x3f | 0x80 );
+ }
+
+ if ( $codepoint < 0x10000 ) {
+ return chr( $codepoint >> 12 & 0x0f | 0xe0 ) .
+ chr( $codepoint >> 6 & 0x3f | 0x80 ) .
+ chr( $codepoint & 0x3f | 0x80 );
+ }
+
+ if ( $codepoint < 0x110000 ) {
+ return chr( $codepoint >> 18 & 0x07 | 0xf0 ) .
+ chr( $codepoint >> 12 & 0x3f | 0x80 ) .
+ chr( $codepoint >> 6 & 0x3f | 0x80 ) .
+ chr( $codepoint & 0x3f | 0x80 );
+ }
+
+ throw new InvalidArgumentException( "Asked for code outside of range ($codepoint)" );
+ }
+
+ /**
+ * Take a series of space-separated hexadecimal numbers representing
+ * Unicode code points and return a UTF-8 string composed of those
+ * characters. Used by UTF-8 data generation and testing routines.
+ *
+ * @param $sequence String
+ * @return String
+ * @throws InvalidArgumentException if fed out of range data.
+ * @private Used in tests and data table generation
+ */
+ public static function hexSequenceToUtf8 ( $sequence ) {
+ $utf = '';
+ foreach ( explode( ' ', $sequence ) as $hex ) {
+ $n = hexdec( $hex );
+ $utf .= self::codepointToUtf8( $n );
+ }
+
+ return $utf;
+ }
+
+ /**
+ * Take a UTF-8 string and return a space-separated series of hex
+ * numbers representing Unicode code points. For debugging.
+ *
+ * @param string $str UTF-8 string.
+ * @return string
+ * @private
+ */
+ private static function utf8ToHexSequence ( $str ) {
+ $buf = '';
+ foreach ( preg_split( '//u', $str, -1, PREG_SPLIT_NO_EMPTY ) as $cp ) {
+ $buf .= sprintf( '%04x ', self::utf8ToCodepoint( $cp ) );
+ }
+
+ return rtrim( $buf );
+ }
+
+ /**
+ * Determine the Unicode codepoint of a single-character UTF-8 sequence.
+ * Does not check for invalid input data.
+ *
+ * @param $char String
+ * @return Integer
+ */
+ public static function utf8ToCodepoint ( $char ) {
+ # Find the length
+ $z = ord( $char[0] );
+ if ( $z & 0x80 ) {
+ $length = 0;
+ while ( $z & 0x80 ) {
+ $length++;
+ $z <<= 1;
+ }
+ } else {
+ $length = 1;
+ }
+
+ if ( $length != strlen( $char ) ) {
+ return false;
+ }
+
+ if ( $length == 1 ) {
+ return ord( $char );
+ }
+
+ # Mask off the length-determining bits and shift back to the original location
+ $z &= 0xff;
+ $z >>= $length;
+
+ # Add in the free bits from subsequent bytes
+ for ( $i = 1; $i < $length; $i++ ) {
+ $z <<= 6;
+ $z |= ord( $char[$i] ) & 0x3f;
+ }
+
+ return $z;
+ }
+
+ /**
+ * Escape a string for inclusion in a PHP single-quoted string literal.
+ *
+ * @param string $string string to be escaped.
+ * @return String: escaped string.
+ */
+ public static function escapeSingleString ( $string ) {
+ return strtr( $string,
+ array(
+ '\\' => '\\\\',
+ '\'' => '\\\''
+ ) );
+ }
+}
diff --git a/vendor/wikimedia/utfnormal/src/Validator.php b/vendor/wikimedia/utfnormal/src/Validator.php
new file mode 100644
index 00000000..2ccfd7ad
--- /dev/null
+++ b/vendor/wikimedia/utfnormal/src/Validator.php
@@ -0,0 +1,770 @@
+<?php
+/**
+ * Unicode normalization routines
+ *
+ * Copyright © 2004 Brion Vibber <brion@pobox.com>
+ * https://www.mediawiki.org/
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ * @ingroup UtfNormal\UtfNormal
+ */
+namespace UtfNormal;
+
+use Normalizer;
+
+/**
+ * @defgroup UtfNormal UtfNormal
+ */
+
+define( 'NORMALIZE_INTL', function_exists( 'normalizer_normalize' ) );
+
+/**
+ * Unicode normalization routines for working with UTF-8 strings.
+ * Currently assumes that input strings are valid UTF-8!
+ *
+ * Not as fast as I'd like, but should be usable for most purposes.
+ * UtfNormal\Validator::toNFC() will bail early if given ASCII text or text
+ * it can quickly determine is already normalized.
+ *
+ * All functions can be called static.
+ *
+ * See description of forms at http://www.unicode.org/reports/tr15/
+ *
+ * @ingroup UtfNormal
+ */
+class Validator {
+ public static $utfCombiningClass = null;
+ public static $utfCanonicalComp = null;
+ public static $utfCanonicalDecomp = null;
+
+ # Load compatibility decompositions on demand if they are needed.
+ public static $utfCompatibilityDecomp = null;
+ public static $utfCheckNFC;
+
+ /**
+ * The ultimate convenience function! Clean up invalid UTF-8 sequences,
+ * and convert to normal form C, canonical composition.
+ *
+ * Fast return for pure ASCII strings; some lesser optimizations for
+ * strings containing only known-good characters. Not as fast as toNFC().
+ *
+ * @param string $string a UTF-8 string
+ * @return string a clean, shiny, normalized UTF-8 string
+ */
+ static function cleanUp ( $string ) {
+ if ( NORMALIZE_INTL ) {
+ $string = self::replaceForNativeNormalize( $string );
+ $norm = normalizer_normalize( $string, Normalizer::FORM_C );
+ if ( $norm === null || $norm === false ) {
+ # normalizer_normalize will either return false or null
+ # (depending on which doc you read) if invalid utf8 string.
+ # quickIsNFCVerify cleans up invalid sequences.
+
+ if ( self::quickIsNFCVerify( $string ) ) {
+ # if that's true, the string is actually already normal.
+ return $string;
+ } else {
+ # Now we are valid but non-normal
+ return normalizer_normalize( $string, Normalizer::FORM_C );
+ }
+ } else {
+ return $norm;
+ }
+ } elseif ( self::quickIsNFCVerify( $string ) ) {
+ # Side effect -- $string has had UTF-8 errors cleaned up.
+ return $string;
+ } else {
+ return self::NFC( $string );
+ }
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form C, canonical composition.
+ * Fast return for pure ASCII strings; some lesser optimizations for
+ * strings containing only known-good characters.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form C
+ */
+ static function toNFC ( $string ) {
+ if ( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_C );
+ elseif ( self::quickIsNFC( $string ) )
+ return $string;
+ else
+ return self::NFC( $string );
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form D, canonical decomposition.
+ * Fast return for pure ASCII strings.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form D
+ */
+ static function toNFD ( $string ) {
+ if ( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_D );
+ elseif ( preg_match( '/[\x80-\xff]/', $string ) )
+ return self::NFD( $string );
+ else
+ return $string;
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form KC, compatibility composition.
+ * This may cause irreversible information loss, use judiciously.
+ * Fast return for pure ASCII strings.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form KC
+ */
+ static function toNFKC ( $string ) {
+ if ( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_KC );
+ elseif ( preg_match( '/[\x80-\xff]/', $string ) )
+ return self::NFKC( $string );
+ else
+ return $string;
+ }
+
+ /**
+ * Convert a UTF-8 string to normal form KD, compatibility decomposition.
+ * This may cause irreversible information loss, use judiciously.
+ * Fast return for pure ASCII strings.
+ *
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string in normal form KD
+ */
+ static function toNFKD ( $string ) {
+ if ( NORMALIZE_INTL )
+ return normalizer_normalize( $string, Normalizer::FORM_KD );
+ elseif ( preg_match( '/[\x80-\xff]/', $string ) )
+ return self::NFKD( $string );
+ else
+ return $string;
+ }
+
+ /**
+ * Load the basic composition data if necessary
+ * @private
+ */
+ static function loadData () {
+ if ( !isset( self::$utfCombiningClass ) ) {
+ require_once __DIR__ . '/UtfNormalData.inc';
+ }
+ }
+
+ /**
+ * Returns true if the string is _definitely_ in NFC.
+ * Returns false if not or uncertain.
+ * @param string $string a valid UTF-8 string. Input is not validated.
+ * @return bool
+ */
+ static function quickIsNFC ( $string ) {
+ # ASCII is always valid NFC!
+ # If it's pure ASCII, let it through.
+ if ( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
+
+ self::loadData();
+ $len = strlen( $string );
+ for ( $i = 0; $i < $len; $i++ ) {
+ $c = $string[$i];
+ $n = ord( $c );
+ if ( $n < 0x80 ) {
+ continue;
+ } elseif ( $n >= 0xf0 ) {
+ $c = substr( $string, $i, 4 );
+ $i += 3;
+ } elseif ( $n >= 0xe0 ) {
+ $c = substr( $string, $i, 3 );
+ $i += 2;
+ } elseif ( $n >= 0xc0 ) {
+ $c = substr( $string, $i, 2 );
+ $i++;
+ }
+ if ( isset( self::$utfCheckNFC[$c] ) ) {
+ # If it's NO or MAYBE, bail and do the slow check.
+ return false;
+ }
+ if ( isset( self::$utfCombiningClass[$c] ) ) {
+ # Combining character? We might have to do sorting, at least.
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns true if the string is _definitely_ in NFC.
+ * Returns false if not or uncertain.
+ * @param string $string a UTF-8 string, altered on output to be valid UTF-8 safe for XML.
+ * @return bool
+ */
+ static function quickIsNFCVerify ( &$string ) {
+ # Screen out some characters that eg won't be allowed in XML
+ $string = preg_replace( '/[\x00-\x08\x0b\x0c\x0e-\x1f]/', Constants::UTF8_REPLACEMENT, $string );
+
+ # ASCII is always valid NFC!
+ # If we're only ever given plain ASCII, we can avoid the overhead
+ # of initializing the decomposition tables by skipping out early.
+ if ( !preg_match( '/[\x80-\xff]/', $string ) ) return true;
+
+ static $checkit = null, $tailBytes = null, $utfCheckOrCombining = null;
+ if ( !isset( $checkit ) ) {
+ # Load/build some scary lookup tables...
+ self::loadData();
+
+ $utfCheckOrCombining = array_merge( self::$utfCheckNFC, self::$utfCombiningClass );
+
+ # Head bytes for sequences which we should do further validity checks
+ $checkit = array_flip( array_map( 'chr',
+ array( 0xc0, 0xc1, 0xe0, 0xed, 0xef,
+ 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
+ 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff ) ) );
+
+ # Each UTF-8 head byte is followed by a certain
+ # number of tail bytes.
+ $tailBytes = array();
+ for ( $n = 0; $n < 256; $n++ ) {
+ if ( $n < 0xc0 ) {
+ $remaining = 0;
+ } elseif ( $n < 0xe0 ) {
+ $remaining = 1;
+ } elseif ( $n < 0xf0 ) {
+ $remaining = 2;
+ } elseif ( $n < 0xf8 ) {
+ $remaining = 3;
+ } elseif ( $n < 0xfc ) {
+ $remaining = 4;
+ } elseif ( $n < 0xfe ) {
+ $remaining = 5;
+ } else {
+ $remaining = 0;
+ }
+ $tailBytes[chr( $n )] = $remaining;
+ }
+ }
+
+ # Chop the text into pure-ASCII and non-ASCII areas;
+ # large ASCII parts can be handled much more quickly.
+ # Don't chop up Unicode areas for punctuation, though,
+ # that wastes energy.
+ $matches = array();
+ preg_match_all(
+ '/([\x00-\x7f]+|[\x80-\xff][\x00-\x40\x5b-\x5f\x7b-\xff]*)/',
+ $string, $matches );
+
+ $looksNormal = true;
+ $base = 0;
+ $replace = array();
+ foreach ( $matches[1] as $str ) {
+ $chunk = strlen( $str );
+
+ if ( $str[0] < "\x80" ) {
+ # ASCII chunk: guaranteed to be valid UTF-8
+ # and in normal form C, so skip over it.
+ $base += $chunk;
+ continue;
+ }
+
+ # We'll have to examine the chunk byte by byte to ensure
+ # that it consists of valid UTF-8 sequences, and to see
+ # if any of them might not be normalized.
+ #
+ # Since PHP is not the fastest language on earth, some of
+ # this code is a little ugly with inner loop optimizations.
+
+ $head = '';
+ $len = $chunk + 1; # Counting down is faster. I'm *so* sorry.
+
+ for ( $i = -1; --$len; ) {
+ $remaining = $tailBytes[$c = $str[++$i]];
+ if ( $remaining ) {
+ # UTF-8 head byte!
+ $sequence = $head = $c;
+ do {
+ # Look for the defined number of tail bytes...
+ if ( --$len && ( $c = $str[++$i] ) >= "\x80" && $c < "\xc0" ) {
+ # Legal tail bytes are nice.
+ $sequence .= $c;
+ } else {
+ if ( 0 == $len ) {
+ # Premature end of string!
+ # Drop a replacement character into output to
+ # represent the invalid UTF-8 sequence.
+ $replace[] = array( Constants::UTF8_REPLACEMENT,
+ $base + $i + 1 - strlen( $sequence ),
+ strlen( $sequence ) );
+ break 2;
+ } else {
+ # Illegal tail byte; abandon the sequence.
+ $replace[] = array( Constants::UTF8_REPLACEMENT,
+ $base + $i - strlen( $sequence ),
+ strlen( $sequence ) );
+ # Back up and reprocess this byte; it may itself
+ # be a legal ASCII or UTF-8 sequence head.
+ --$i;
+ ++$len;
+ continue 2;
+ }
+ }
+ } while ( --$remaining );
+
+ if ( isset( $checkit[$head] ) ) {
+ # Do some more detailed validity checks, for
+ # invalid characters and illegal sequences.
+ if ( $head == "\xed" ) {
+ # 0xed is relatively frequent in Korean, which
+ # abuts the surrogate area, so we're doing
+ # this check separately to speed things up.
+
+ if ( $sequence >= Constants::UTF8_SURROGATE_FIRST ) {
+ # Surrogates are legal only in UTF-16 code.
+ # They are totally forbidden here in UTF-8
+ # utopia.
+ $replace[] = array( Constants::UTF8_REPLACEMENT,
+ $base + $i + 1 - strlen( $sequence ),
+ strlen( $sequence ) );
+ $head = '';
+ continue;
+ }
+ } else {
+ # Slower, but rarer checks...
+ $n = ord( $head );
+ if (
+ # "Overlong sequences" are those that are syntactically
+ # correct but use more UTF-8 bytes than are necessary to
+ # encode a character. Naïve string comparisons can be
+ # tricked into failing to see a match for an ASCII
+ # character, for instance, which can be a security hole
+ # if blacklist checks are being used.
+ ( $n < 0xc2 && $sequence <= Constants::UTF8_OVERLONG_A )
+ || ( $n == 0xe0 && $sequence <= Constants::UTF8_OVERLONG_B )
+ || ( $n == 0xf0 && $sequence <= Constants::UTF8_OVERLONG_C )
+
+ # U+FFFE and U+FFFF are explicitly forbidden in Unicode.
+ || ( $n == 0xef &&
+ ( $sequence == Constants::UTF8_FFFE )
+ || ( $sequence == Constants::UTF8_FFFF ) )
+
+ # Unicode has been limited to 21 bits; longer
+ # sequences are not allowed.
+ || ( $n >= 0xf0 && $sequence > Constants::UTF8_MAX )
+ ) {
+
+ $replace[] = array( Constants::UTF8_REPLACEMENT,
+ $base + $i + 1 - strlen( $sequence ),
+ strlen( $sequence ) );
+ $head = '';
+ continue;
+ }
+ }
+ }
+
+ if ( isset( $utfCheckOrCombining[$sequence] ) ) {
+ # If it's NO or MAYBE, we'll have to rip
+ # the string apart and put it back together.
+ # That's going to be mighty slow.
+ $looksNormal = false;
+ }
+
+ # The sequence is legal!
+ $head = '';
+ } elseif ( $c < "\x80" ) {
+ # ASCII byte.
+ $head = '';
+ } elseif ( $c < "\xc0" ) {
+ # Illegal tail bytes
+ if ( $head == '' ) {
+ # Out of the blue!
+ $replace[] = array( Constants::UTF8_REPLACEMENT, $base + $i, 1 );
+ } else {
+ # Don't add if we're continuing a broken sequence;
+ # we already put a replacement character when we looked
+ # at the broken sequence.
+ $replace[] = array( '', $base + $i, 1 );
+ }
+ } else {
+ # Miscellaneous freaks.
+ $replace[] = array( Constants::UTF8_REPLACEMENT, $base + $i, 1 );
+ $head = '';
+ }
+ }
+ $base += $chunk;
+ }
+ if ( count( $replace ) ) {
+ # There were illegal UTF-8 sequences we need to fix up.
+ $out = '';
+ $last = 0;
+ foreach ( $replace as $rep ) {
+ list( $replacement, $start, $length ) = $rep;
+ if ( $last < $start ) {
+ $out .= substr( $string, $last, $start - $last );
+ }
+ $out .= $replacement;
+ $last = $start + $length;
+ }
+ if ( $last < strlen( $string ) ) {
+ $out .= substr( $string, $last );
+ }
+ $string = $out;
+ }
+
+ return $looksNormal;
+ }
+
+ # These take a string and run the normalization on them, without
+ # checking for validity or any optimization etc. Input must be
+ # VALID UTF-8!
+ /**
+ * @param $string string
+ * @return string
+ * @private
+ */
+ static function NFC ( $string ) {
+ return self::fastCompose( self::NFD( $string ) );
+ }
+
+ /**
+ * @param $string string
+ * @return string
+ * @private
+ */
+ static function NFD ( $string ) {
+ self::loadData();
+
+ return self::fastCombiningSort(
+ self::fastDecompose( $string, self::$utfCanonicalDecomp ) );
+ }
+
+ /**
+ * @param $string string
+ * @return string
+ * @private
+ */
+ static function NFKC ( $string ) {
+ return self::fastCompose( self::NFKD( $string ) );
+ }
+
+ /**
+ * @param $string string
+ * @return string
+ * @private
+ */
+ static function NFKD ( $string ) {
+ if ( !isset( self::$utfCompatibilityDecomp ) ) {
+ require_once __DIR__ . '/UtfNormalDataK.inc';
+ }
+
+ return self::fastCombiningSort(
+ self::fastDecompose( $string, self::$utfCompatibilityDecomp ) );
+ }
+
+ /**
+ * Perform decomposition of a UTF-8 string into either D or KD form
+ * (depending on which decomposition map is passed to us).
+ * Input is assumed to be *valid* UTF-8. Invalid code will break.
+ * @private
+ * @param string $string valid UTF-8 string
+ * @param array $map hash of expanded decomposition map
+ * @return string a UTF-8 string decomposed, not yet normalized (needs sorting)
+ */
+ static function fastDecompose ( $string, $map ) {
+ self::loadData();
+ $len = strlen( $string );
+ $out = '';
+ for ( $i = 0; $i < $len; $i++ ) {
+ $c = $string[$i];
+ $n = ord( $c );
+ if ( $n < 0x80 ) {
+ # ASCII chars never decompose
+ # THEY ARE IMMORTAL
+ $out .= $c;
+ continue;
+ } elseif ( $n >= 0xf0 ) {
+ $c = substr( $string, $i, 4 );
+ $i += 3;
+ } elseif ( $n >= 0xe0 ) {
+ $c = substr( $string, $i, 3 );
+ $i += 2;
+ } elseif ( $n >= 0xc0 ) {
+ $c = substr( $string, $i, 2 );
+ $i++;
+ }
+ if ( isset( $map[$c] ) ) {
+ $out .= $map[$c];
+ continue;
+ } else {
+ if ( $c >= Constants::UTF8_HANGUL_FIRST && $c <= Constants::UTF8_HANGUL_LAST ) {
+ # Decompose a hangul syllable into jamo;
+ # hardcoded for three-byte UTF-8 sequence.
+ # A lookup table would be slightly faster,
+ # but adds a lot of memory & disk needs.
+ #
+ $index = ( ( ord( $c[0] ) & 0x0f ) << 12
+ | ( ord( $c[1] ) & 0x3f ) << 6
+ | ( ord( $c[2] ) & 0x3f ) )
+ - Constants::UNICODE_HANGUL_FIRST;
+ $l = intval( $index / Constants::UNICODE_HANGUL_NCOUNT );
+ $v = intval(
+ ( $index % Constants::UNICODE_HANGUL_NCOUNT )
+ / Constants::UNICODE_HANGUL_TCOUNT
+ );
+ $t = $index % Constants::UNICODE_HANGUL_TCOUNT;
+ $out .= "\xe1\x84" . chr( 0x80 + $l ) . "\xe1\x85" . chr( 0xa1 + $v );
+ if ( $t >= 25 ) {
+ $out .= "\xe1\x87" . chr( 0x80 + $t - 25 );
+ } elseif ( $t ) {
+ $out .= "\xe1\x86" . chr( 0xa7 + $t );
+ }
+ continue;
+ }
+ }
+ $out .= $c;
+ }
+
+ return $out;
+ }
+
+ /**
+ * Sorts combining characters into canonical order. This is the
+ * final step in creating decomposed normal forms D and KD.
+ * @private
+ * @param string $string a valid, decomposed UTF-8 string. Input is not validated.
+ * @return string a UTF-8 string with combining characters sorted in canonical order
+ */
+ static function fastCombiningSort ( $string ) {
+ self::loadData();
+ $len = strlen( $string );
+ $out = '';
+ $combiners = array();
+ $lastClass = -1;
+ for ( $i = 0; $i < $len; $i++ ) {
+ $c = $string[$i];
+ $n = ord( $c );
+ if ( $n >= 0x80 ) {
+ if ( $n >= 0xf0 ) {
+ $c = substr( $string, $i, 4 );
+ $i += 3;
+ } elseif ( $n >= 0xe0 ) {
+ $c = substr( $string, $i, 3 );
+ $i += 2;
+ } elseif ( $n >= 0xc0 ) {
+ $c = substr( $string, $i, 2 );
+ $i++;
+ }
+ if ( isset( self::$utfCombiningClass[$c] ) ) {
+ $lastClass = self::$utfCombiningClass[$c];
+ if ( isset( $combiners[$lastClass] ) ) {
+ $combiners[$lastClass] .= $c;
+ } else {
+ $combiners[$lastClass] = $c;
+ }
+ continue;
+ }
+ }
+ if ( $lastClass ) {
+ ksort( $combiners );
+ $out .= implode( '', $combiners );
+ $combiners = array();
+ }
+ $out .= $c;
+ $lastClass = 0;
+ }
+ if ( $lastClass ) {
+ ksort( $combiners );
+ $out .= implode( '', $combiners );
+ }
+
+ return $out;
+ }
+
+ /**
+ * Produces canonically composed sequences, i.e. normal form C or KC.
+ *
+ * @private
+ * @param string $string a valid UTF-8 string in sorted normal form D or KD.
+ * Input is not validated.
+ * @return string a UTF-8 string with canonical precomposed characters used
+ * where possible.
+ */
+ static function fastCompose ( $string ) {
+ self::loadData();
+ $len = strlen( $string );
+ $out = '';
+ $lastClass = -1;
+ $lastHangul = 0;
+ $startChar = '';
+ $combining = '';
+ $x1 = ord( substr( Constants::UTF8_HANGUL_VBASE, 0, 1 ) );
+ $x2 = ord( substr( Constants::UTF8_HANGUL_TEND, 0, 1 ) );
+ for ( $i = 0; $i < $len; $i++ ) {
+ $c = $string[$i];
+ $n = ord( $c );
+ if ( $n < 0x80 ) {
+ # No combining characters here...
+ $out .= $startChar;
+ $out .= $combining;
+ $startChar = $c;
+ $combining = '';
+ $lastClass = 0;
+ continue;
+ } elseif ( $n >= 0xf0 ) {
+ $c = substr( $string, $i, 4 );
+ $i += 3;
+ } elseif ( $n >= 0xe0 ) {
+ $c = substr( $string, $i, 3 );
+ $i += 2;
+ } elseif ( $n >= 0xc0 ) {
+ $c = substr( $string, $i, 2 );
+ $i++;
+ }
+ $pair = $startChar . $c;
+ if ( $n > 0x80 ) {
+ if ( isset( self::$utfCombiningClass[$c] ) ) {
+ # A combining char; see what we can do with it
+ $class = self::$utfCombiningClass[$c];
+ if ( !empty( $startChar ) &&
+ $lastClass < $class &&
+ $class > 0 &&
+ isset( self::$utfCanonicalComp[$pair] )
+ ) {
+ $startChar = self::$utfCanonicalComp[$pair];
+ $class = 0;
+ } else {
+ $combining .= $c;
+ }
+ $lastClass = $class;
+ $lastHangul = 0;
+ continue;
+ }
+ }
+ # New start char
+ if ( $lastClass == 0 ) {
+ if ( isset( self::$utfCanonicalComp[$pair] ) ) {
+ $startChar = self::$utfCanonicalComp[$pair];
+ $lastHangul = 0;
+ continue;
+ }
+ if ( $n >= $x1 && $n <= $x2 ) {
+ # WARNING: Hangul code is painfully slow.
+ # I apologize for this ugly, ugly code; however
+ # performance is even more teh suck if we call
+ # out to nice clean functions. Lookup tables are
+ # marginally faster, but require a lot of space.
+ #
+ if ( $c >= Constants::UTF8_HANGUL_VBASE &&
+ $c <= Constants::UTF8_HANGUL_VEND &&
+ $startChar >= Constants::UTF8_HANGUL_LBASE &&
+ $startChar <= Constants::UTF8_HANGUL_LEND
+ ) {
+ #
+ #$lIndex = utf8ToCodepoint( $startChar ) - UNICODE_HANGUL_LBASE;
+ #$vIndex = utf8ToCodepoint( $c ) - UNICODE_HANGUL_VBASE;
+ $lIndex = ord( $startChar[2] ) - 0x80;
+ $vIndex = ord( $c[2] ) - 0xa1;
+
+ $hangulPoint = Constants::UNICODE_HANGUL_FIRST +
+ Constants::UNICODE_HANGUL_TCOUNT *
+ ( Constants::UNICODE_HANGUL_VCOUNT * $lIndex + $vIndex );
+
+ # Hardcode the limited-range UTF-8 conversion:
+ $startChar = chr( $hangulPoint >> 12 & 0x0f | 0xe0 ) .
+ chr( $hangulPoint >> 6 & 0x3f | 0x80 ) .
+ chr( $hangulPoint & 0x3f | 0x80 );
+ $lastHangul = 0;
+ continue;
+ } elseif ( $c >= Constants::UTF8_HANGUL_TBASE &&
+ $c <= Constants::UTF8_HANGUL_TEND &&
+ $startChar >= Constants::UTF8_HANGUL_FIRST &&
+ $startChar <= Constants::UTF8_HANGUL_LAST &&
+ !$lastHangul
+ ) {
+ # $tIndex = utf8ToCodepoint( $c ) - UNICODE_HANGUL_TBASE;
+ $tIndex = ord( $c[2] ) - 0xa7;
+ if ( $tIndex < 0 ) $tIndex = ord( $c[2] ) - 0x80 + ( 0x11c0 - 0x11a7 );
+
+ # Increment the code point by $tIndex, without
+ # the function overhead of decoding and recoding UTF-8
+ #
+ $tail = ord( $startChar[2] ) + $tIndex;
+ if ( $tail > 0xbf ) {
+ $tail -= 0x40;
+ $mid = ord( $startChar[1] ) + 1;
+ if ( $mid > 0xbf ) {
+ $startChar[0] = chr( ord( $startChar[0] ) + 1 );
+ $mid -= 0x40;
+ }
+ $startChar[1] = chr( $mid );
+ }
+ $startChar[2] = chr( $tail );
+
+ # If there's another jamo char after this, *don't* try to merge it.
+ $lastHangul = 1;
+ continue;
+ }
+ }
+ }
+ $out .= $startChar;
+ $out .= $combining;
+ $startChar = $c;
+ $combining = '';
+ $lastClass = 0;
+ $lastHangul = 0;
+ }
+ $out .= $startChar . $combining;
+
+ return $out;
+ }
+
+ /**
+ * This is just used for the benchmark, comparing how long it takes to
+ * interate through a string without really doing anything of substance.
+ * @param $string string
+ * @return string
+ */
+ static function placebo ( $string ) {
+ $len = strlen( $string );
+ $out = '';
+ for ( $i = 0; $i < $len; $i++ ) {
+ $out .= $string[$i];
+ }
+
+ return $out;
+ }
+
+ /**
+ * Function to replace some characters that we don't want
+ * but most of the native normalize functions keep.
+ *
+ * @param string $string The string
+ * @return String String with the character codes replaced.
+ */
+ private static function replaceForNativeNormalize ( $string ) {
+ $string = preg_replace(
+ '/[\x00-\x08\x0b\x0c\x0e-\x1f]/',
+ Constants::UTF8_REPLACEMENT,
+ $string );
+ $string = str_replace( Constants::UTF8_FFFE, Constants::UTF8_REPLACEMENT, $string );
+ $string = str_replace( Constants::UTF8_FFFF, Constants::UTF8_REPLACEMENT, $string );
+
+ return $string;
+ }
+}
diff --git a/vendor/zordius/lightncandy/.scrutinizer.yml b/vendor/zordius/lightncandy/.scrutinizer.yml
new file mode 100644
index 00000000..471470fe
--- /dev/null
+++ b/vendor/zordius/lightncandy/.scrutinizer.yml
@@ -0,0 +1,95 @@
+filter:
+ excluded_paths: [tests/*, build/*]
+
+checks:
+ php:
+ code_rating: true
+ duplication: true
+ variable_existence: true
+ useless_calls: true
+ use_statement_alias_conflict: true
+ unused_variables: true
+ unused_properties: true
+ unused_parameters: true
+ unused_methods: true
+ unreachable_code: true
+ sql_injection_vulnerabilities: true
+ security_vulnerabilities: true
+ precedence_mistakes: true
+ precedence_in_conditions: true
+ parameter_non_unique: true
+ no_property_on_interface: true
+ no_non_implemented_abstract_methods: true
+ deprecated_code_usage: true
+ closure_use_not_conflicting: true
+ closure_use_modifiable: true
+ avoid_useless_overridden_methods: true
+ avoid_conflicting_incrementers: true
+ assignment_of_null_return: true
+ verify_property_names: true
+ verify_argument_usable_as_reference: true
+ use_self_instead_of_fqcn: true
+ uppercase_constants: true
+ too_many_arguments: true
+ switch_fallthrough_commented: true
+ spacing_of_function_arguments: true
+ spacing_around_non_conditional_operators: true
+ spacing_around_conditional_operators: true
+ space_after_cast: true
+ single_namespace_per_use: true
+ simplify_boolean_return: true
+ side_effects_or_types: true
+ scope_indentation:
+ spaces_per_level: '4'
+ return_doc_comments: true
+ return_doc_comment_if_not_inferrable: true
+ require_scope_for_properties: true
+ require_scope_for_methods: true
+ require_php_tag_first: true
+ require_closing_tag: true
+ prefer_while_loop_over_for_loop: true
+ prefer_unix_line_ending: true
+ prefer_sapi_constant: true
+ parameter_doc_comments: true
+ param_doc_comment_if_not_inferrable: true
+ more_specific_types_in_doc_comments: true
+ overriding_private_members: true
+ optional_parameters_at_the_end: true
+ non_commented_empty_catch_block: true
+ no_unnecessary_if: true
+ no_unnecessary_function_call_in_for_loop: true
+ no_unnecessary_final_modifier: true
+ lowercase_php_keywords: true
+ lowercase_basic_constants: true
+ avoid_unnecessary_concatenation: true
+ avoid_fixme_comments: true
+ avoid_duplicate_types: true
+ avoid_corrupting_byteorder_marks: true
+ argument_type_checks: true
+ avoid_superglobals: true
+ avoid_perl_style_comments: true
+ avoid_multiple_statements_on_same_line: true
+ avoid_length_functions_in_loops: true
+ missing_arguments: true
+ method_calls_on_non_object: true
+ no_mixed_inline_html: true
+ no_goto: true
+ no_exit: true
+ no_eval: true
+ no_error_suppression: true
+ no_empty_statements: true
+ no_elseif_statements: true
+ no_duplicate_arguments: true
+ no_debug_code: true
+
+tools:
+ external_code_coverage: true
+ php_code_coverage: true
+ php_cs_fixer: true
+ php_code_sniffer: true
+ php_cpd: true
+ php_hhvm: true
+ php_mess_detector: true
+ php_analyzer: true
+ php_pdepend: true
+ sensiolabs_security_checker: true
diff --git a/vendor/zordius/lightncandy/CONTRIBUTING.md b/vendor/zordius/lightncandy/CONTRIBUTING.md
new file mode 100644
index 00000000..0d7aa65d
--- /dev/null
+++ b/vendor/zordius/lightncandy/CONTRIBUTING.md
@@ -0,0 +1,18 @@
+# How to Contribute
+
+## Reporting Issues
+
+Submit your issue here: https://github.com/zordius/lightncandy/issues/new
+
+Proper sample input data with template is prefered. If you can provide the LightnCandy version (or commit hash) and some sample code of your setup/helpers it will be better.
+
+## Pull Requests
+
+Pull request is another good way. Before you submit your patch, please ensure you run full tests:
+
+```sh
+git submodule init
+git submodule update
+build/runphp build/gen_test.php
+phpunit
+```
diff --git a/vendor/zordius/lightncandy/HISTORY.md b/vendor/zordius/lightncandy/HISTORY.md
new file mode 100644
index 00000000..577f953d
--- /dev/null
+++ b/vendor/zordius/lightncandy/HISTORY.md
@@ -0,0 +1,183 @@
+HISTORY
+=======
+
+master current trunk
+ * align with handlebars.js master
+
+v0.17 https://github.com/zordius/lightncandy/tree/v0.17
+ * 7bcce4c1a7 support {{@last}} for {{#each}} on both object and array.
+ * b0c44c3b40 remove ending \n in lightncandy.php
+ * e130875d5a support single quoted string input: {{foo 'bar'}}.
+ * c603aa39d8 support `renderex` to extend anything in render function.
+ * f063e5302c now render function debug constants works well in standalone mode.
+ * 53f6a6816d fix parsing bug when there is a `=` inside single quoted string.
+ * 2f16c0c393 now really autoload when installed with composer.
+ * c4da1f576c supports {{^myHelper}}.
+
+v0.17 https://github.com/zordius/lightncandy/tree/v0.17
+ * 3b48a0acf7 fix parsing bug when FLAG_NOESCAPE enabled
+ * 5c774b1b08 fix hbhelpers response error with options['fn'] when FLAG_BESTPERFORMANCE enabled
+ * c60fe70bdb fix hbhelpers response error with options['inverse'] when FLAG_BESTPERFORMANCE enabled
+ * e19b3e3426 provide options['root'] and options['_parent'] to hbhelpers
+ * d8a288e83b refine variable parsing logic to support {{@../index}}, {{@../key}}, etc.
+
+v0.16 https://github.com/zordius/lightncandy/tree/v0.16
+ * align with handlebars.js 2.0.0
+ * 4f036aff62 better error message for named arguments.
+ * 0b462a387b support {{#with var}} ... {{else}} ... {{/with}}.
+ * 4ca624f651 fix 1 ANSI code error.
+ * 01ea3e9f42 support instances with PHP __call magic funciton.
+ * 38059036a7 support {{#foo}} or {{#each foo}} on PHP Traversable instance.
+ * 366f5ec0ac add FLAG_MUSTACHESP and FLAG_MUSTACHEPAIN into FLAG_HANDLEBARS and FLAG_HANDLEBARSJS now.
+ * b61d7b4a81 align with handlebars.js standalone tags behavior.
+ * b211e1742e now render false as 'false'.
+ * 655a2485be fix bug for {{helper "==="}}
+ * bb58669162 support FLAG_NOESCAPE
+
+v0.15 https://github.com/zordius/lightncandy/tree/v0.15
+ * align with handlebars.js 2.0.0
+ * 4c750806e8 fix for \ in template
+ * 12ab6626d6 support escape. \{{foo}} will be rendered as is. ( handlebars spec , require FLAG_SLASH )
+ * 876bd44d9c escape &#x60; to &amp;#x60; ( require FLAG_JSQUOTE )
+ * f1f388ed79 support {{^}} as {{else}} ( require FLAG_ELSE )
+ * d5e17204b6 support {{#each}} == {{#each .}} now.
+ * 742126b440 fix {{>foo/bar}} partial not found bug.
+ * d62c261ff9 support numbers as helper input {{helper 0.1 -1.2}}
+ * d40c76b84f support escape in string arguments {{helper "test \" double quote"}}
+ * ecb57a2348 fix for missing partial in partial bug.
+ * 1adad5dbfa fix {{#with}} error when FLAG_WITH not used.
+ * ffd5e35c2d fix error when rendering array value as {{.}} without FLAG_JSOBJECT.
+ * bd4987adbd support changing context on partial {{>foo bar}} ( require FLAG_RUNTIMEPARTIAL )
+ * f5decaa7e3 support name sarguments on partial {{>foo bar name=tee}} . fix {{..}} bug.
+ * c20bb36457 support `partials` in options.
+ * e8779dbe8c change default `basedir` hehavior, stop partial files lookup when do not prodive `basedir` in options.
+ * c4e3401fe4 fix {{>"test"}} or {{>[test]}} or {{>1234}} bug.
+ * e59f62ea9b fix seciton behavior when input is object, and add one new flag: FLAG_MUSTACHESEC.
+ * 80eaf8e007 use static::method not self::method for subclass.
+ * 0bad5c8f20 fix usedFeature generation bugs
+
+v0.14 https://github.com/zordius/lightncandy/tree/v0.14
+ * align with handlebars.js 2.0.0-alpha.4
+ * fa6225f278 support boolen value in named arguments for cusotm helper
+ * 160743e1c8 better error message when unmatch {{/foo}} tag detected
+ * d9a9416907 support {{&foo}}
+ * 8797485cfa fix {{^foo}} logic when foo is empty list
+ * 523b1373c4 fix handlebars custom helper interface
+ * a744a2d522 fix bad syntax when FLAG_RENDER_DEBUG + helpers
+ * 0044f7bd10 change FLAG_THIS behavoir
+ * b5b0739b68 support recursive context lookup now ( mustache spec , require FLAG_MUSTACHELOOKUP )
+ * 096c241fce support standalone tag detection now ( mustache spec , require FLAG_MUSTACHESP )
+ * cea46c9a67 support {{=<% %>=}} to set delimiter
+ * 131696af11 support subexpression {{helper (helper2 foo) bar}}
+ * 5184d41be6 support runtime/recursive partial ( require FLAG_RUNTIMEPARTIAL )
+ * 6408917f76 support partial indent ( mustache spec , require FLAG_MUSTACHEPAIN )
+
+v0.13 https://github.com/zordius/lightncandy/tree/v0.13
+ * align with handlebars.js 2.0.0-alpha.4
+ * e5a8fe3833 fix issue #46 ( error with {{this.foo.bar}} )
+ * ea131512f9 fix issue #44 ( error with some helper inline function PHP code syntax )
+ * 522591a0c6 fix issue #49 ( error with some helper user function PHP code syntax )
+ * c4f7e1eaac support {{foo.bar}} lookup on instance foo then property/method bar ( flagd FLAG_PROPERTY or FLAG_METHOD required )
+ * 0f4c0daa4b stop simulate Javascript output for array when pass input to custom helpers
+ * 22d07e5f0f BIG CHANGE of custom helper interface
+
+v0.12 https://github.com/zordius/lightncandy/tree/v0.12
+ * align with handlebars.js 2.0.0-alpha.2
+ * 64db34cf65 support {{@first}} and {{@last}}
+ * bfa1fbef97 add new flag FLAG_SPVARS
+ * 10a4623dc1 remove json schema support
+ * 240d9fa290 only export used LCRun2 functions when compile() with FLAG_STANDALONE now
+ * 3fa897c98c rename LCRun2 to LCRun3 for interface changed, old none standalone templates will error with newer version.
+ * e0838c7418 now can output debug template map with ANSI color
+ * 80dbeab63d fix php warning when compile with custom helper or block custom helper
+ * 8ce6268b64 support Handlebars.js style custom helper
+
+v0.11 https://github.com/zordius/lightncandy/tree/v0.11
+ * align with handlebars.js 2.0.0-alpha.2
+ * a275d52c97 use php array, remove val().
+ * 8834914c2a only export used custom helper into render function now
+ * eb6d82d871 refine option flag consts
+ * fc437295ed refine comments for phpdoc
+ * fbf116c3e2 fix for tailing ; after helper functions
+ * f47a2d5014 fix for wrong param when new Exception
+ * 94e71ebcbd add isset() check for input value
+ * a826b8a1ab support {{else}} in {{#each}} now
+ * 25dac11bb7 support {{!-- comments --}} now (this extension allow }} apperas in the comments)
+ * e142b6e116 support {{@root}} or {{@root.foo.bar}} now
+ * 58c8d84aa2 custom helper can return extra flag to change html encoded behavior now
+
+v0.10 https://github.com/zordius/lightncandy/tree/v0.10
+ * align with handlebars.js 2.0.0-alpha.1
+ * 4c9f681080 file name changed: lightncandy.inc => lightncandy.php
+ * e3de01081c some minor fix for json schema
+ * 1feec458c7 new variable handling logic, save variable name parsing time when render() . rendering performance improved 10~30%!
+ * 3fa897c98c rename LCRun to LCRun2 for interface changed, old none standalone templates will error with newer version.
+ * 43a6d33717 fix for {{../}} php warning message
+ * 9189ebc1e4 now auto push documents from Travis CI
+ * e077d0b631 support named arguments for custom helpers {{helper name=value}}
+ * 2331b6fe55 support block custom helpers
+ * 4fedaa25f7 support number value as named arguments
+ * 6a91ab93d2 fix for default options and php warnings
+ * fc157fde62 fix for doblue quoted arguments (issue #15)
+
+v0.9 https://github.com/zordius/lightncandy/tree/v0.9
+ * align with handlebars.js 1.3
+ * a55f2dd067 support both {{@index}} and {{@key}} when {{#each an_object}}
+ * e59f931ea7 add FLAG_JSQUOTE support
+ * 92b3cf58af report more than 1 error when compile()
+ * 93cc121bcf test for wrong variable name format in test/error.php
+ * 41c1b431b4 support advanced variable naming {{foo.[bar].10}} now
+ * 15ce1a00a8 add FLAG_EXTHELPER option
+ * f51337bde2 support space control {{~sometag}} or {{sometag~}}
+ * fe3d67802e add FLAG_SPACECTL option
+ * 920fbb3039 support custom helper
+ * 07ae71a1bf migrate into Travis CI
+ * ddd3335ff6 support "some string" argument
+ * 20f6c888d7 html encode after custom helper executed
+ * 10a2f45fdc add test generator
+ * ccd1d3ddc2 migrate to Scrutinizer , change file name LightnCandy.inc to LightnCandy.php
+ * 5ac8ad8d04 now is a Composer package
+
+v0.8 https://github.com/zordius/lightncandy/tree/v0.8
+ * align with handlebars.js 1.0.12
+ * aaec049 fix partial in partial not works bug
+ * 52706cc fix for {{#var}} and {{^var}} , now when var === 0 means true
+ * 4f7f816 support {{@key}} and {{@index}} in {{#each var}}
+ * 63aef2a prevent array_diff_key() PHP warning when {{#each}} on none array value
+ * 10f3d73 add more is_array() check when {{#each}} and {{#var}}
+ * 367247b fix {{#if}} and {{#unless}} when value is an empty array
+ * c76c0bb returns null if var is not exist in a template [contributed by dtelyukh@github.com]
+ * d18bb6d add FLAG_ECHO support
+ * aec2b2b fix {{#if}} and {{#unless}} when value is an empty string
+ * 8924604 fix variable output when false in an array
+ * e82c324 fix for ifv and ifvar logic difference
+ * 1e38e47 better logic on var name checking. now support {{0}} in the loop, but it is not handlebars.js standard
+
+v0.7 https://github.com/zordius/lightncandy/tree/v0.7
+ * add HISTORY.md
+ * 777304c change compile format to include in val, isec, ifvar
+ * 55de127 support {{../}} in {{#each}}
+ * 57e90af fix parent levels detection bug
+ * 96bb4d7 fix bugs for {{#.}} and {{#this}}
+ * f4217d1 add ifv and unl 2 new methods for LCRun
+ * 3f1014c fix {{#this}} and {{#.}} bug when used with {{../var}}
+ * cbf0b28 fix {{#if}} logic error when using {{../}}
+ * 2b20ef8 fix {{#with}} + {{../}} bug
+ * 540cd44 now support FLAG_STANDALONE
+ * 67ac5ff support {{>partial}}
+ * 98c7bb1 detect unclosed token now
+
+v0.6 https://github.com/zordius/lightncandy/tree/v0.6
+ * align with handlebarsjs 1.0.11
+ * 45ac3b6 fix #with bug when var is false
+ * 1a46c2c minor #with logic fix. update document
+ * fdc753b fix #each and section logic for 018-hb-withwith-006
+ * e6cc95a add FLAG_PARENT, detect template error when scan()
+ * 1980691 make new LCRun::val() method to normal path.val logic
+ * 110d24f {{#if path.var}} bug fixed
+ * d6ae2e6 fix {{#with path.val}} when input value is null
+ * 71cf074 fix for 020-hb-doteach testcase
+
+v0.5 https://github.com/zordius/lightncandy/tree/v0.5
+ * 955aadf fix #each bug when input is a hash
+ * final version for following handlebarsjs 1.0.7
diff --git a/vendor/zordius/lightncandy/LICENSE.txt b/vendor/zordius/lightncandy/LICENSE.txt
new file mode 100644
index 00000000..3df560d4
--- /dev/null
+++ b/vendor/zordius/lightncandy/LICENSE.txt
@@ -0,0 +1,19 @@
+Copyrights for code authored by Yahoo! Inc. is licensed under the following
+terms:
+MIT License
+Copyright (c) 2013, 2014 Yahoo! Inc. All Rights Reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/zordius/lightncandy/README.md b/vendor/zordius/lightncandy/README.md
new file mode 100644
index 00000000..2afd6b9c
--- /dev/null
+++ b/vendor/zordius/lightncandy/README.md
@@ -0,0 +1,670 @@
+LightnCandy
+===========
+
+An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).
+
+Travis CI status: [![Unit testing](https://travis-ci.org/zordius/lightncandy.svg?branch=master)](https://travis-ci.org/zordius/lightncandy) [![Regression testing](https://travis-ci.org/zordius/HandlebarsTest.svg?branch=master)](https://travis-ci.org/zordius/HandlebarsTest)
+
+Scrutinizer CI status: [![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/zordius/lightncandy.svg)](https://scrutinizer-ci.com/g/zordius/lightncandy/)
+
+Package on packagist: [![Latest Stable Version](https://poser.pugx.org/zordius/lightncandy/v/stable.svg)](https://packagist.org/packages/zordius/lightncandy) [![License](https://poser.pugx.org/zordius/lightncandy/license.svg)](https://github.com/zordius/lightncandy/blob/master/LICENSE.txt) [![Total Downloads](https://poser.pugx.org/zordius/lightncandy/downloads.svg)](https://packagist.org/packages/zordius/lightncandy) [![HHVM Status](http://hhvm.h4cc.de/badge/zordius/lightncandy.svg)](http://hhvm.h4cc.de/package/zordius/lightncandy)
+
+Features
+--------
+
+* Logicless template: mustache ( http://mustache.github.com/ ) or handlebars ( http://handlebarsjs.com/ ) .
+* Compile template to **pure PHP** code. Examples:
+ * <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/001-simple-vars.tmpl">Template A</a> generated <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/001-simple-vars.php">PHP A</a>
+ * <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/016-hb-eachthis.tmpl">Template B</a> generated <a href="https://github.com/zordius/HandlebarsTest/blob/master/fixture/016-hb-eachthis.php">PHP B</a>
+* **FAST!**
+ * Runs 4~6 times faster than <a href="https://github.com/bobthecow/mustache.php">mustache.php</a>.
+ * Runs 4~10 times faster than <a href="https://github.com/dingram/mustache-php">mustache-php</a>.
+ * Runs 10~30 times faster than <a href="https://github.com/XaminProject/handlebars.php">handlebars.php</a>.
+ * Detail performance test reports can be found <a href="https://github.com/zordius/HandlebarsTest">here</a>, go http://zordius.github.io/HandlebarsTest/ to see charts.
+* **SMALL!** single PHP file, only 110K!
+* **ROBUST!**
+ * 100% support <a href="https://github.com/mustache/spec">mustache spec v1.1.2</a> (without lambda module)
+ * Supports almost all <a href="https://github.com/kasperisager/handlebars-spec">handlebars.js spec</a>
+ * Output <a href="https://github.com/zordius/HandlebarsTest/blob/master/FEATURES.md">SAME</a> with <a href="https://github.com/wycats/handlebars.js">handlebars.js</a>
+* **FLEXIBLE!**
+ * Lot of <a href="#compile-options">options</a> to change features and behaviors.
+* Context generation
+ * Analyze used features from your template (use `LightnCandy::getContext()` to get it) .
+* Debug
+ * <a href="#template-debugging">Generate debug version template</a>
+ * Find out missing data when rendering template.
+ * Generate visually debug template.
+* Standalone Template
+ * The compiled PHP code can run without any PHP library. You do not need to include LightnCandy when execute rendering function.
+
+Installation
+------------
+
+Use Composer ( https://getcomposer.org/ ) to install LightnCandy:
+
+```
+composer require zordius/lightncandy:dev-master
+```
+
+Or, download LightnCandy from github:
+
+```
+curl -LO https://github.com/zordius/lightncandy/raw/master/src/lightncandy.php
+```
+
+LightnCandy requirement: PHP 5.3.0+ .
+
+**UPGRADE NOTICE**
+
+* Please check <a href="HISTORY.md">HISTORY.md</a> for versions history.
+* Please check <a href="UPGRADE.md">UPGRADE.md</a> for upgrade notice.
+
+Usage
+-----
+```php
+// THREE STEPS TO USE LIGHTNCANDY
+// Step 1. require the lib, compile template, and get the PHP code as string
+require('src/lightncandy.php');
+
+$template = "Welcome {{name}} , You win \${{value}} dollars!!\n";
+$phpStr = LightnCandy::compile($template); // compiled PHP code in $phpStr
+
+// Step 2A. (Usage 1) use LightnCandy::prepare to get rendering function
+// DEPRECATED , it may require PHP setting allow_url_fopen=1 ,
+// and allow_url_fopen=1 is not secure .
+// When allow_url_fopen = 0, prepare() will create tmp file then include it,
+// you will need to add your tmp directory into open_basedir.
+// YOU MAY NEED TO CHANGE PHP SETTING BY THIS WAY
+$renderer = LightnCandy::prepare($phpStr);
+
+
+// Step 2B. (Usage 2) Store your render function in a file
+// You decide your compiled template file path and name, save it.
+// You can load your render function by include() later.
+// RECOMMENDED WAY
+file_put_contents($php_inc, $phpStr);
+$renderer = include($php_inc);
+
+
+// Step 3. run native PHP render function any time
+echo "Template is:\n$template\n\n";
+echo $renderer(Array('name' => 'John', 'value' => 10000));
+echo $renderer(Array('name' => 'Peter', 'value' => 1000));
+```
+
+The output will be:
+
+```
+Template is:
+Welcome {{name}} , You win ${{value}} dollars!!
+
+
+Welcome John , You win $10000 dollars!!
+Welcome Peter , You win $1000 dollars!!
+```
+
+Compile Options
+---------------
+
+You can apply more options by running `LightnCandy::compile($template, $options)`:
+
+```php
+LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_STANDALONE
+));
+```
+
+Default is to compile the template as PHP, which can be run as fast as possible (flags = `FLAG_BESTPERFORMANCE`).
+
+* `FLAG_ERROR_LOG` : error_log() when found any template error
+* `FLAG_ERROR_EXCEPTION` : throw exception when found any template error
+* `FLAG_ERROR_SKIPPARTIAL` : skip 'partial not found' error/exception. Use this to align with mustache specification.
+* `FLAG_NOESCAPE` : do not do any HTML escape on {{var}}.
+* `FLAG_STANDALONE` : generate stand-alone PHP codes, which can be execute without including LightnCandy.php. The compiled PHP code will contain scoped user function, somehow larger. And, the performance of the template will slow 1 ~ 10%.
+* `FLAG_JSTRUE` : generate 'true' or 'false' when value is true or false (JavaScript behavior). Otherwise, true/false will generate ''.
+* `FLAG_JSOBJECT` : generate '[object Object]' for associated array, generate ',' separated values for array (JavaScript behavior). Otherwise, all PHP array will generate '' or 'Array'.
+* `FLAG_THIS` : resolve `{{this}}` as `{{.}}` in template. Otherwise, `{{this}}` will be resolved as normal variable.
+* `FLAG_WITH` : support `{{#with var}}` in template. Otherwise, `{{#with var}}` will cause template error.
+* `FLAG_PARENT` : support `{{../var}}` in template. Otherwise, `{{../var}}` will cause template error.
+* `FLAG_JSQUOTE` : escape `'` to `&#x27;` , <code>&#x60;</code> to `&#x60;` . Otherwise, `'` will be escaped to `&#039;` , <code>&#x60;</code> will not be touched.
+* `FLAG_ADVARNAME` : support `{{foo.[0].[#te#st].bar}}` style advanced variable naming in template. Use this flag if you wanna use `"some string"` or `(subexpresssion)` as argument.
+* `FLAG_NAMEDARG` : support named arguments for custom helper `{{helper name1=val1 nam2=val2 ...}}`.
+* `FLAG_EXTHELPER` : do not including custom helper codes into compiled PHP codes. This reduces the code size, but you need to take care of your helper functions when rendering. If you forget to include required functions when execute rendering function, `undefined function` runtime error will be triggered. NOTE: Anonymous functions will always be placed into generated codes.
+* `FLAG_RUNTIMEPARTIAL` : compile partial as runtime function, This enables recursive partials or context change for partials.
+* `FLAG_SLASH` : Skip a delimiter when it behind `\` .
+* `FLAG_ELSE` : support `{{else}}` or `{{^}}` as handlebars specification. Otherwise, `{{else}}` will be resolved as normal variable , and {{^}} will cause template error.
+* `FLAG_PROPERTY` : support object instance attribute access.
+* `FLAG_METHOD` : support object instance method access.
+* `FLAG_INSTANCE` : same with `FLAG_PROPERTY` + `FLAG_METHOD`
+* `FLAG_SPACECTL` : support space control `{{~ }}` or `{{ ~}}` in template. Otherwise, `{{~ }}` or `{{ ~}}` will cause template error.
+* `FLAG_SPVARS` : support special variables include @root, @index, @key, @first, @last. Otherwise, compile these variable names with default parsing logic.
+* `FLAG_JS` : simulate all JavaScript string conversion behavior, same with `FLAG_JSTRUE` + `FLAG_JSOBJECT`.
+* `FLAG_HANDLEBARS` : support all handlebars extensions (which mustache do not supports) , same with `FLAG_THIS` + `FLAG_WITH` + `FLAG_PARENT` + `FLAG_JSQUOTE` + `FLAG_ADVARNAME` + `FLAG_NAMEDARG` + `FLAG_SLASH` + `FLAG_ELSE` + `FLAG_MUSTACHESP` + `FLAG_MUSTACHEPAIN`.
+* `FLAG_HANDLEBARSJS` : align with handlebars.js behaviors, same with `FLAG_JS` + `FLAG_HANDLEBARS`.
+* `FLAG_MUSTACHESP` : align line change and spacing behaviors with mustache specification.
+* `FLAG_MUSTACHELOOKUP` : align recursive lookup up behaviors with mustache specification.
+* `FLAG_MUSTACHEPAIN` : align partial indent behavior with mustache specification.
+* `FLAG_MUSTACHESEC` : align section `{{#foo}}` context behavior with mustache specification.
+* `FLAG_MUSTACHE` : support all mustache specification, same with `FLAG_ERROR_SKIPPARTIAL` + `FLAG_MUSTACHESP` + `FLAG_MUSTACHELOOKUP` + `FLAG_MUSTACHEPAIN` + `FLAG_MUSTACHESEC`.
+* `FLAG_ECHO` : compile to `echo 'a', $b, 'c';` to improve performance. This will slow down rendering when the template and data are simple, but will improve 1% ~ 7% when the data is big and looping in the template.
+* `FLAG_BESTPERFORMANCE` : same with `FLAG_ECHO` now. This flag may be changed base on performance testing result in the future.
+* `FLAG_RENDER_DEBUG` : generate debug template to show error when rendering. With this flag, the performance of rendering may be slowed.
+
+Partial Support
+---------------
+
+LightnCandy supports partial when compile time. You can provide partials by `partials` option when `compile()`:
+
+```php
+LightnCandy::compile($template, Array(
+ 'partials' => Array(
+ 'name' => 'template: {{name}}',
+ ),
+));
+```
+
+You can also provide partials by files. When `compile()`, LightnCandy will search template files from `basedir` in the option if you provided one or more. Default template file name is `*.tmpl`, you can change or add more template file extensions with `fileext` option.
+
+```php
+// Loading partial from file system only when valid directory is provided by basedir option
+// '.' means getpwd()
+LightnCandy::compile($template, Array(
+ 'basedir' => '.'
+));
+
+// Multiple basedir and fileext are supported
+LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_STANDALONE,
+ 'basedir' => Array(
+ '/usr/local/share/handlebars/templates',
+ '/usr/local/share/my_project/templates',
+ '/usr/local/share/my_project/partials',
+ ),
+ 'fileext' => Array(
+ '.tmpl',
+ '.mustache',
+ '.handlebars',
+ )
+));
+```
+
+With this setting, when you include a partial by `{{> partial_name}}`, LightnCandy will search in this order:
+* /usr/local/share/handlebars/templates/partial_name.tmpl
+* /usr/local/share/handlebars/templates/partial_name.mustache
+* /usr/local/share/handlebars/templates/partial_name.handlebars
+* /usr/local/share/my_project/templates/partial_name.tmpl
+* /usr/local/share/my_project/templates/partial_name.mustache
+* /usr/local/share/my_project/templates/partial_name.handlebars
+* /usr/local/share/my_project/partials/partial_name.tmpl
+* /usr/local/share/my_project/partials/partial_name.mustache
+* /usr/local/share/my_project/partials/partial_name.handlebars
+
+By default, partial uses the same context with original template. If you want to change context for the partial, you may add one more argument after the partial name:
+
+```
+{{>partial_name .}} // Same as {{>partial_name}}
+{{>partial_name foo}} // Change input context to foo, FLAG_RUNTIMEPARTIAL required
+{{>partial_name ..}} // use {{..}} as new input context, FLAG_RUNTIMEPARTIAL required
+```
+
+Custom Helper
+-------------
+
+Custom helper can help you deal with common template tasks, for example: provide URL and text then generate a link. To know more about custom helper, you can read original handlebars.js document here: http://handlebarsjs.com/expressions.html .
+
+**NOTICE**: custom helpers to handle single tag `{{xxx}}` or a section `{{#yyy}} ... {{/yyy}}` are absolutely different in LightnCandy. To know more about creating custom helpers to handle `{{#yyy}} ... {{/yyy}}`, please refer to <a href="#block-custom-helper">Block Custom Helper</a>.
+
+When `compile()`, LightnCandy will lookup helpers from generated custom helper name table. You can register custom helpers with `helpers` option (**NOTICE**: `FLAG_NAMEDARG` is required for named arguments, `FLAG_ADVARNAME` is required for string or subexpression arguments):
+
+```php
+LightnCandy::compile($template, Array(
+ // FLAG_NAMEDARG is required if you want to use named arguments
+ 'flags' => LightnCandy::FLAG_HANDLEBARS
+ 'helpers' => Array(
+ // 1. You may pass your function name
+ // When the function is not exist, you get compile time error
+ // In this case, the helper name is same with function name
+ // Template: {{my_helper_functoin ....}}
+ 'my_helper_function',
+
+ // 2. You may also provide a static call from a class
+ // In this case, the helper name is same with provided full name
+ // **DEPRECATED** It is not valid in handlebars.js
+ // Template: {{myClass::myStaticMethod ....}}
+ 'myClass::myStaticMethod',
+
+ // 3. You may also provide an alias name for helper function
+ // This help you to mapping different function to a preferred helper name
+ // Template: {{helper_name ....}}
+ 'helper_name' => 'my_other_helper',
+
+ // 4. Alias also works well for static call of a class
+ // This help you to mapping different function to a preferred helper name
+ // Template: {{helper_name2 ....}}
+ 'helper_name2' => 'myClass::func',
+
+ // 5. Anonymous function should be provided with alias
+ // The function will be included in generaed code always
+ // Template: {{helper_name3 ....}}
+ 'helper_name3' => function ($arg1, $arg2) {
+ return "<a href=\"{$arg1}\">{$arg2}</a>";
+ }
+ )
+));
+```
+
+Custom Helper Interface
+-----------------------
+
+The input arguments are processed by LightnCandy automatically, you do not need to worry about variable name processing or current context. You can also use double quoted string as input:
+
+```
+{{{helper name}}} // This send processed {{{name}}} into the helper
+{{{helper ../name}}} // This send processed {{{../name}}} into the helper
+{{{helper "Test"}}} // This send the string "Test" into the helper (FLAG_ADVARNAME is required)
+{{helper "Test"}} // This send the string "Test" into the helper and escape the helper result
+{{{helper "Test" ../name}}} // This send string "Test" as first parameter,
+ // and processed {{{../name}}} as second parameter into the helper
+```
+
+Your custom helper function will be executed with two arguments. The first one is noname arguments, the second one is named arguments:
+
+```php
+function myhelper ($args, $named) {
+ if (count($args)) {
+ // handle no name arguments....
+ }
+ // access foo=bar from $named['foo'] ...
+}
+```
+
+In your template:
+
+```
+{{{helper name=value}}} // This send processed {{{value}}} into $named['name']
+{{{helper name="value"}}} // This send the string "value" into $named['name']
+{{{helper [na me]="value"}}} // You can still protect the name with [ ]
+ // so you get $named['na me'] as the string 'value'
+{{{helper url name="value"}}} // This send processed {{{url}}} into $args[0]
+ // and the string "value" into $named['name']
+```
+
+Custom Helper Escaping
+----------------------
+
+The return value of your custom helper should be a string. When your custom helper be executed from {{ }} , the return value will be HTML escaped. You may execute your helper by {{{ }}} , then the original helper return value will be outputted directly.
+
+When you need to do different escaping logic, you can return extended information by Array($responseString, $escape_flag) , here are some custom helper return value cases:
+
+```php
+// escaping is handled by lightncandy and decided by template
+// if the helper is in {{ }} , you get 'The U&amp;ME Helper is ececuted!'
+// if the helper is in {{{ }}} , you get 'The U&ME Helper is executed!'
+return 'The U&ME Helper is executed!';
+
+// Same as above because the escape_flag is DEFAULT
+// 0, false, null, undefined, or '' means DEFAULT
+return Array('The U&ME Helper is executed!');
+return Array('The U&ME Helper is executed!', false);
+return Array('The U&ME Helper is executed!', 0);
+
+// escaping is handled by the helper, lightncandy will do nothing
+// No matter in {{ }} or {{{ }}} , you get 'Exact&Same output \' \" Ya!'
+return Array('Exact&Same output \' " Ya!', 'raw');
+
+// force lightncandy escaping the helper result
+// No matter in {{ }} or {{{ }}} , you get 'Not&amp;Same output &#039; &quot; Ya!'
+return Array('Not&Same output \' " Ya!', 'enc');
+
+// force lightncandy escaping the helper result in handlebars.js way
+// No matter in {{ }} or {{{ }}} , you get 'Not&amp;Same output &#x27; &quot; Ya!'
+return Array('Not&Same output \' " Ya!', 'encq');
+```
+
+Block Custom Helper
+-------------------
+
+Block custom helper must be used as a section, the section is started with `{{#helper_name ...}}` and ended with `{{/helper_name}}`.
+
+You may use block custom helper to:
+
+1. Provide advanced condition logic which is different from `{{#if ...}}` ... `{{/if}}` .
+2. Modify current context for the inner block.
+3. Provide different context to the inner block.
+
+You can register block custom helpers with `blockhelpers` option:
+
+```php
+LightnCandy::compile($template, Array(
+ 'blockhelpers' => Array( // The usage of blockhelpers option is similar with helpers option.
+ 'my_helper_function', // You can use function name, class name with static method,
+ ... // and choose preferred helper name by providing key name.
+ )
+));
+```
+
+Block Custom Helper Interface
+-----------------------------
+
+LightnCandy handled all input arguments for you, you will receive current context and parsed arguments. The return value of helper function will become new context then be passed into inner block. If you do not return any value, or return null, the inner block will not be rendered. For example:
+
+```php
+// Only render inner block when input > 5
+// {{#helper_iffivemore total_people}}More then 5 people, discount!{{/helper_iffivemore}}
+function helper_iffivemore($cx, $args, $named) {
+ return $args[0] > 5 ? $cx : null;
+}
+
+// You can use named arguments, too
+// {{#helper_if value=people logic="more" tovalue=5}}Yes the logic is true{{/helper_if}}
+function helper_if($cx, $args, $named) {
+ switch ($args['logic']) {
+ case 'more':
+ return $named['value'] > $named['tovalue'] ? $cx : null;
+ case 'less':
+ return $named['value'] < $named['tovalue'] ? $cx : null;
+ case 'eq':
+ return $named['value'] == $named['tovalue'] ? $cx : null;
+ }
+}
+
+// Provide default values for name and salary
+// {{#helper_defaultpeople}}Hello, {{name}} ....Your salary will be {{salary}}{{/helper_defaultpeople}}
+function helper_defaultpeople($cx, $args, $named) {
+ if (!isset($cx['name'])) {
+ $cx['name'] = 'Sir';
+ }
+ $cx['salary'] = isset($cx['salary']) ? '$' . $cx['salary'] : 'unknown';
+ return $cx;
+}
+
+// Provide specific context to innerblock
+// {{#helper_sample}}Preview Name:{{name}} , Salary:{{salary}}.{{/helper_sample}}
+function helper_sample($cx, $args) {
+ return Array('name' => 'Sample Name', 'salary' => 'Sample Salary');
+}
+```
+
+You cannot provide new rendered result, nor handle loop in your block custom helper. To provide different rendering result, you should use <a href="#custom-helper">custom helper</a>. To handle loop, you should use `{{#each}}` . For example:
+
+```php
+// Provide specific context to innerblock, then loop on it.
+// {{#helper_categories}}{{#each .}}<li><a href="?id={{id}}">{{name}}</a></li>{{/each}}{{/helper_categories}}
+function helper_categories($cx, $args) {
+ return getMyCategories(); // Array('category1', 'category2', ...)
+}
+```
+
+The mission of a block custom helper is only focus on providing different context or logic to inner block, nothing else. If you like to do things beyond these restrictions, please using `hbhelpers` and keep reading to next section.
+
+Handlebars.js' Custom Helper
+----------------------------
+
+You can implement helpers more like Handlebars.js way with `hbhelpers` option, all matched single custom helper and block custom helper will be handled. In Handlebars.js, a block custom helper can rendener child block by executing options->fn, and change context by send new context as first parameter. Here are some examples to explain the behavior of `hbhelpers` custom helper:
+
+**#mywith (context change)**
+* LightnCandy
+```php
+// LightnCandy sample, #mywith works same with #with
+$php = LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'hbhelpers' => Array(
+ 'mywith' => function ($context, $options) {
+ return $options['fn']($context);
+ }
+ )
+));
+```
+
+* Handlebars.js
+```javascript
+// Handlebars.js sample, #mywith works same with #with
+Handlebars.registerHelper('mywith', function(context, options) {
+ return options.fn(context);
+});
+```
+
+**#myeach (context change)**
+* LightnCandy
+```php
+// LightnCandy sample, #myeach works same with #each
+$php = LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'hbhelpers' => Array(
+ 'myeach' => function ($context, $options) {
+ $ret = '';
+ foreach ($context as $cx) {
+ $ret .= $options['fn']($cx);
+ }
+ return $ret;
+ }
+ )
+));
+```
+
+* Handlebars.js
+```javascript
+// Handlebars.js sample, #myeach works same with #each
+Handlebars.registerHelper('myeach', function(context, options) {
+ var ret = '', i, j = context.length;
+ for (i = 0; i < j; i++) {
+ ret = ret + options.fn(context[i]);
+ }
+ return ret;
+});
+```
+
+**#myif (no context change)**
+* LightnCandy
+```php
+// LightnCandy sample, #myif works same with #if
+$php = LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'hbhelpers' => Array(
+ 'myif' => function ($conditional, $options) {
+ if ($conditional) {
+ return $options['fn']();
+ } else {
+ return $options['inverse']();
+ }
+ }
+ )
+));
+```
+
+* Handlebars.js
+```javascript
+// Handlebars.js sample, #myif works same with #if
+Handlebars.registerHelper('myif', function(conditional, options) {
+ if (conditional) {
+ return options.fn(this);
+ } else {
+ return options.inverse(this);
+ }
+});
+```
+
+**Hashed arguments**
+* LightnCandy
+```php
+$php = LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'hbhelpers' => Array(
+ 'sample' => function ($arg1, $arg2, $options) {
+ // All hashed arguments are in $options['hash']
+ }
+ )
+));
+```
+
+* Handlebars.js
+```javascript
+Handlebars.registerHelper('sample', function(arg1, arg2, options) {
+ // All hashed arguments are in options.hash
+});
+```
+
+**Escaping**
+
+When a Handlebars.js style custom helper be used as block tags, LightnCandy will not escape the result. When it is a single {{...}} tag, LightnCandy will escape the result. To change the escape behavior, you can return extended information by Array(), please read <a href="#custom-helper-escaping">Custom Helper Escaping</a> for more.
+
+Template Debugging
+------------------
+
+When template error happened, LightnCandy::compile() will return false. You may compile with `FLAG_ERROR_LOG` to see more error message, or compile with `FLAG_ERROR_EXCEPTION` to catch the exception.
+
+You may generate debug version of templates with `FLAG_RENDER_DEBUG` when compile() . The debug template contained more debug information and slower (TBD: performance result) , you may pass extra LCRun3 options into render function to know more rendering error (missing data). For example:
+
+```php
+$template = "Hello! {{name}} is {{gender}}.
+Test1: {{@root.name}}
+Test2: {{@root.gender}}
+Test3: {{../test3}}
+Test4: {{../../test4}}
+Test5: {{../../.}}
+Test6: {{../../[test'6]}}
+{{#each .}}
+each Value: {{.}}
+{{/each}}
+{{#.}}
+section Value: {{.}}
+{{/.}}
+{{#if .}}IF OK!{{/if}}
+{{#unless .}}Unless not OK!{{/unless}}
+";
+
+// compile to debug version
+$php = LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_RENDER_DEBUG | LightnCandy::FLAG_HANDLEBARSJS
+));
+
+// Get the render function
+$renderer = LightnCandy::prepare($php);
+
+// error_log() when missing data:
+// LCRun3: [gender] is not exist
+// LCRun3: ../[test] is not exist
+$renderer(Array('name' => 'John'), LCRun3::DEBUG_ERROR_LOG);
+
+// Output visual debug template with ANSI color:
+echo $renderer(Array('name' => 'John'), LCRun3::DEBUG_TAGS_ANSI);
+
+// Output debug template with HTML comments:
+echo $renderer(Array('name' => 'John'), LCRun3::DEBUG_TAGS_HTML);
+```
+
+The ANSI output will be:
+
+<a href="tests/example_debug.php"><img src="example_debug.png"/></a>
+
+Here are the list of LCRun3 debug options for render function:
+
+* `DEBUG_ERROR_LOG` : error_log() when missing required data
+* `DEBUG_ERROR_EXCEPTION` : throw exception when missing required data
+* `DEBUG_TAGS` : turn the return value of render function into normalized mustache tags
+* `DEBUG_TAGS_ANSI` : turn the return value of render function into normalized mustache tags with ANSI color
+* `DEBUG_TAGS_HTML` : turn the return value of render function into normalized mustache tags with HTML comments
+
+Customize Render Function
+-------------------------
+
+If you want to do extra tasks inside render function or add more comment, you may use `renderex` when `compile()` . For example, this sample embed the compile time comment into the template:
+
+```php
+$php = LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'renderex' => '// Compiled at ' . date('Y-m-d h:i:s')
+));
+```
+
+Your render function will be:
+
+```php
+function ($in) {$
+ $cx = array(...);
+ // compiled at 1999-12-31 00:00:00
+ return .....
+}
+```
+
+Please make sure the passed in `renderex` is valid PHP, LightnCandy will not check it.
+
+Unsupported Feature (so far)
+----------------------------
+
+* [NEVER] `{{foo/bar}}` style variable name, it is deprecated in official handlebars.js document.
+* [maybe] mustache lambda : runtime time compile based on input value is far from lightncandy nature, not in the plan now.
+
+Suggested Handlebars Template Practices
+---------------------------------------
+
+* Prevent to use `{{#with}}` . I think `{{path.to.val}}` is more readable then `{{#with path.to}}{{val}}{{/with}}`; when using `{{#with}}` you will confusing on scope changing. `{{#with}}` only save you very little time when you access many variables under same path, but cost you a lot time when you need to understand then maintain a template.
+* use `{{{val}}}` when you do not require HTML escaped output on the value. It is better performance, too.
+* If you wanna display `{{`, use this: `{{{"{{"}}}`, prevent using `\{{`.
+* Prevent to use custom helper if you want to reuse your template in different language. Or, you may need to implement different versions of helper in different languages.
+* For best performance, you should only use 'compile on demand' pattern when you are in development stage. Before you go to production, you can `LightnCandy::compile()` on all your templates, save all generated PHP codes, and deploy these generated files (You may need to maintain a build process for this) . **DO NOT COMPILE ON PRODUCTION** , it also a best practice for security. Adding cache for 'compile on demand' is not the best solution. If you want to build some library or framework based on LightnCandy, think about this scenario.
+* Recompile your templates when you upgrade LightnCandy every time.
+
+Detail Feature list
+-------------------
+
+Go http://handlebarsjs.com/ to see more feature description about handlebars.js. All features align with it.
+
+* Exact same CR/LF behavior with handlebars.js
+* Exact same CR/LF bahavior with mustache spec (require `FLAG_MUSTACHESP`)
+* Exact same 'true' or 'false' output with handlebars.js (require `FLAG_JSTRUE`)
+* Exact same '[object Object]' output or join(',' array) output with handlebars.js (require `FLAG_JSOBJECT`)
+* Can place heading/tailing space, tab, CR/LF inside `{{ var }}` or `{{{ var }}}`
+* Indent behavior of the partial same with mustache spec (require `FLAG_MUSTACHEPAIN`)
+* Recursive variable lookup to parent context behavior same with mustache spec (require `FLAG_MUSTACHELOOKUP`)
+* `{{{value}}}` or `{{&value}}` : raw variable
+ * true as 'true' (require `FLAG_JSTRUE`)
+ * false as 'false' (require `FLAG_TRUE`)
+* `{{value}}` : HTML escaped variable
+ * true as 'true' (require `FLAG_JSTRUE`)
+ * false as 'false' (require `FLAG_JSTRUE`)
+* `{{{path.to.value}}}` : dot notation, raw
+* `{{path.to.value}}` : dot notation, HTML escaped
+* `{{.}}` : current context, HTML escaped
+* `{{{.}}}` : current context, raw
+* `{{this}}` : current context, HTML escaped (require `FLAG_THIS`)
+* `{{{this}}}` : current context, raw (require `FLAG_THIS`)
+* `{{#value}}` : section
+ * false, undefined and null will skip the section
+ * true will run the section with original scope
+ * All others will run the section with new scope (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...)
+* `{{/value}}` : end section
+* `{{^value}}` : inverted section
+ * false, undefined and null will run the section with original scope
+ * All others will skip the section (includes 0, 1, -1, '', '1', '0', '-1', 'false', Array, ...)
+* `{{! comment}}` : comment
+* `{{!-- comment or {{ or }} --}}` : extended comment that can contain }} or {{ .
+* `{{=<% %>=}}` : set delimiter to custom string , the custom string can not contain `=` . Check http://mustache.github.io/mustache.5.html for more example.
+* `{{#each var}}` : each loop
+* `{{#each}}` : each loop on {{.}}
+* `{{/each}}` : end loop
+* `{{#if var}}` : run if logic with original scope (null, false, empty Array and '' will skip this block)
+* `{{/if}}` : end if
+* `{{else}}` or `{{^}}` : run else logic, should between `{{#if var}}` and `{{/if}}` ; or between `{{#unless var}}` and `{{/unless}}`; or between `{{#foo}}` and `{{/foo}}`; or between `{{#each var}}` and `{{/each}}`; or between `{{#with var}}` and `{{/with}}`. (require `FLAG_ELSE`)
+* `{{#unless var}}` : run unless logic with original scope (null, false, empty Array and '' will render this block)
+* `{{#with var}}` : change context scope. If the var is false, skip included section. (require `FLAG_WITH`)
+* `{{../var}}` : parent template scope. (require `FLAG_PARENT`)
+* `{{>file}}` : partial; include another template inside a template.
+* `{{>file foo}}` : partial with new context (require `FLAG_RUNTIMEPARTIAL`)
+* `{{@index}}` : references to current index in a `{{#each}}` loop on an array. (require `FLAG_SPVARS`)
+* `{{@key}}` : references to current key in a `{{#each}}` loop on an object. (require `FLAG_SPVARS`)
+* `{{@root}}` : references to root context. (require `FLAG_SPVARS`)
+* `{{@first}}` : true when looping at first item. (require `FLAG_SPVARS`)
+* `{{@last}}` : true when looping at last item. (require `FLAG_SPVARS`)
+* `{{@root.path.to.value}}` : references to root context then follow the path. (require `FLAG_SPVARS`)
+* `{{@../index}}` : access to parent loop index. (require `FLAG_SPVARS` and `FLAG_PARENT`)
+* `{{@../key}}` : access to parent loop key. (require `FLAG_SPVARS` and `FLAG_PARENT`)
+* `{{foo.[ba.r].[#spec].0.ok}}` : references to $CurrentConext['foo']['ba.r']['#spec'][0]['ok'] . (require `FLAG_ADVARNAME`)
+* `{{~any_valid_tag}}` : Space control, remove all previous spacing (includes CR/LF, tab, space; stop on any none spacing character) (require `FLAG_SPACECTL`)
+* `{{any_valid_tag~}}` : Space control, remove all next spacing (includes CR/LF, tab, space; stop on any none spacing character) (require `FLAG_SPACECTL`)
+* `{{{helper var}}}` : Execute custom helper then render the result
+* `{{helper var}}` : Execute custom helper then render the HTML escaped result
+* `{{helper "str"}}` or `{{helper 'str'}}` : Execute custom helper with string arguments (require `FLAG_ADVARNAME`)
+* `{{helper name1=var name2=var2}}` : Execute custom helper with named arguments (require `FLAG_NAMEDARG`)
+* `{{#helper ...}}...{{/helper}}` : Execute block custom helper
+* `{{helper (helper2 foo) bar}}` : Execute custom helpers as subexpression (require `FLAG_ADVARNAME`)
diff --git a/vendor/zordius/lightncandy/UPGRADE.md b/vendor/zordius/lightncandy/UPGRADE.md
new file mode 100644
index 00000000..01f4c15b
--- /dev/null
+++ b/vendor/zordius/lightncandy/UPGRADE.md
@@ -0,0 +1,18 @@
+Upgrade Notice
+==============
+
+* Standalone templates compiled by older LightnCandy can be executed safe when you upgrade to any new version of LightnCandy.
+
+* Recompile your none standalone templates when you upgrade LightnCandy.
+
+Version v0.13
+-------------
+* The interface of custom helpers was changed from v0.13 . if you use this feature you may need to modify your custom helper functions.
+
+Version v0.11
+-------------
+* Due to big change of render() debugging, the rendering support class `LCRun2` is renamed to `LCRun3`. If you compile templates as none standalone PHP code by LightnCandy v0.11 or before, you should compile these templates again. Or, you may run into `Class 'LCRun2' not found` error when you execute these old rendering functions.
+
+Version v0.9
+------------
+* Due to big change of variable name handling, the rendering support class `LCRun` is renamed to `LCRun2`. If you compile templates as none standalone PHP code by LightnCandy v0.9 or before, you should compile these templates again. Or, you may run into `Class 'LCRun' not found` error when you execute these old rendering functions.
diff --git a/vendor/zordius/lightncandy/build/gen_doc b/vendor/zordius/lightncandy/build/gen_doc
new file mode 100644
index 00000000..428e619d
--- /dev/null
+++ b/vendor/zordius/lightncandy/build/gen_doc
@@ -0,0 +1,6 @@
+#!/bin/sh
+curl -O https://cloud.github.com/downloads/apigen/apigen/ApiGen-2.8.0-standalone.zip
+unzip -oq ApiGen-2.8.0-standalone.zip
+rm ApiGen-2.8.0-standalone.zip
+php -dopen_basedir=/ apigen/apigen.php --source src/ --destination build/result/docs/ --template-config apigen/templates/bootstrap/config.neon --deprecated yes
+rm -rf apigen
diff --git a/vendor/zordius/lightncandy/build/gen_test.php b/vendor/zordius/lightncandy/build/gen_test.php
new file mode 100644
index 00000000..97d49489
--- /dev/null
+++ b/vendor/zordius/lightncandy/build/gen_test.php
@@ -0,0 +1,64 @@
+<?php
+
+foreach (Array(
+ 'vendor/phpunit/phpunit/PHPUnitPHPUnit/Autoload.php',
+ 'PHPUnit/Autoload.php',
+ 'src/lightncandy.php'
+) as $inc) {
+ if (file_exists($inc)) {
+ include_once($inc);
+ break;
+ }
+}
+
+genTestForClass('LightnCandy');
+genTestForClass('LCRun3');
+
+function genTestForClass($classname) {
+ ob_start();
+
+ echo <<<VAR
+<?php
+/**
+ * Generated by build/gen_test
+ */
+require_once('src/lightncandy.php');
+
+class {$classname}Test extends PHPUnit_Framework_TestCase
+{
+
+VAR
+ ;
+
+ $class = new ReflectionClass($classname);
+ foreach ($class->getMethods() as $method) {
+ if (preg_match_all('/@expect (.+) when input (.+)( after (.+))?/', $method->getDocComment(), $matched)) {
+ echo <<<VAR
+ /**
+ * @covers {$classname}::{$method->name}
+ */
+ public function testOn_{$method->name}() {
+ \$method = new ReflectionMethod('$classname', '{$method->name}');
+
+VAR
+ ;
+ if ($method->isPrivate() || $method->isProtected()) {
+ echo " \$method->setAccessible(true);\n";
+ }
+ foreach ($matched[1] as $idx => $expect) {
+ if ($matched[3][$idx]) {
+ echo " {$matched[3][$idx]}\n";
+ }
+ echo " \$this->assertEquals($expect, \$method->invoke(null,\n {$matched[2][$idx]}\n ));\n";
+ }
+ echo " }\n";
+ }
+ }
+ echo "}\n?>";
+
+ $fn = "tests/{$classname}Test.php";
+ if (!file_put_contents($fn, ob_get_clean())) {
+ die("Can not generate tests into file $fn !!\n");
+ }
+}
+?>
diff --git a/vendor/zordius/lightncandy/build/push_ghpage b/vendor/zordius/lightncandy/build/push_ghpage
new file mode 100644
index 00000000..de64e2b9
--- /dev/null
+++ b/vendor/zordius/lightncandy/build/push_ghpage
@@ -0,0 +1,12 @@
+#!/bin/sh
+git checkout -B gh-pages
+git pull origin gh-pages
+rm *
+rm -rf src
+rm -rf test
+cp -r build/result/docs/* .
+rm -rf build
+rm .travis.yml
+git add .
+git commit -a -m "New documents on github"
+git push origin gh-pages
diff --git a/vendor/zordius/lightncandy/build/runphp b/vendor/zordius/lightncandy/build/runphp
new file mode 100644
index 00000000..aa694de3
--- /dev/null
+++ b/vendor/zordius/lightncandy/build/runphp
@@ -0,0 +1,2 @@
+#!/bin/sh
+php -dopen_basedir=/ $1 $2
diff --git a/vendor/zordius/lightncandy/build/travis_push b/vendor/zordius/lightncandy/build/travis_push
new file mode 100644
index 00000000..b51e4dcc
--- /dev/null
+++ b/vendor/zordius/lightncandy/build/travis_push
@@ -0,0 +1,60 @@
+#!/bin/sh
+echo "DEBUG ENV: ${TRAVIS_JOB_NUMBER} , ${TRAVIS_BUILD_NUMBER} , ${TRAVIS_PULL_REQUEST} ..."
+
+if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
+ echo "This is a PR, skip push."
+ exit 0
+fi
+
+if [ "${TRAVIS_BUILD_NUMBER}.1" != "${TRAVIS_JOB_NUMBER}" ]; then
+ echo "Only push documents 1 time... quit."
+ exit 0
+fi
+
+# Push coverage report
+wget https://scrutinizer-ci.com/ocular.phar
+php ocular.phar code-coverage:upload --format=php-clover coverage.clover
+
+# Set for all push in this script.
+git config --global user.name "Travis-CI"
+git config --global user.email "zordius@yahoo-inc.com"
+
+# Generate ANSI sample
+git clone https://github.com/fcambus/ansilove
+php tests/example_debug.php > example_debug
+php ansilove/ansilove example_debug
+git add example_debug.png
+
+# Push new tests back to this branch
+git commit -a -m "Auto generated tests from Travis [ci skip]"
+git push "https://${GHTK}@github.com/zordius/lightncandy.git" HEAD:${TRAVIS_BRANCH} > /dev/null 2>&1
+
+# Update hash in HandlebarsTest and push back, trigger new tests there.
+git clone https://github.com/zordius/HandlebarsTest
+cd HandlebarsTest
+echo ${TRAVIS_COMMIT} > lightncandy
+git add lightncandy
+git commit -a -m "Auto test on zordius/lightncandy@${TRAVIS_COMMIT}"
+git push "https://${GHTK}@github.com/zordius/HandlebarsTest.git" > /dev/null 2>&1
+cd ..
+
+# Generate documents for this branch
+build/gen_doc
+cd build/result/docs
+
+if [ "${TRAVIS_BRANCH}" != "master" ]; then
+ echo "Document will be pushed here: http://zordius.github.io/lightncandy/${TRAVIS_BRANCH}/"
+ cd ..
+ git init
+ git pull --quiet "https://${GHTK}@github.com/zordius/lightncandy.git" gh-pages:master > /dev/null 2>&1
+ rm -rf $TRAVIS_BRANCH
+ mv docs $TRAVIS_BRANCH
+ git add $TRAVIS_BRANCH
+else
+ echo "Document will be pushed here: http://zordius.github.io/lightncandy/"
+ git init
+ git add .
+fi
+
+git commit -m "Auto deployed to Github Pages from branch ${TRAVIS_BRANCH} @${TRAVIS_COMMIT} [ci skip]"
+git push --force --quiet "https://${GHTK}@github.com/zordius/lightncandy.git" master:gh-pages > /dev/null 2>&1
diff --git a/vendor/zordius/lightncandy/composer.json b/vendor/zordius/lightncandy/composer.json
new file mode 100644
index 00000000..bd15cc2c
--- /dev/null
+++ b/vendor/zordius/lightncandy/composer.json
@@ -0,0 +1,22 @@
+{
+ "name": "zordius/lightncandy",
+ "description": "An extremely fast PHP implementation of handlebars ( http://handlebarsjs.com/ ) and mustache ( http://mustache.github.io/ ).",
+ "homepage": "https://github.com/zordius/lightncandy",
+ "keywords": ["handlebars", "mustache", "PHP", "template", "logicless"],
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Zordius Chen",
+ "email": "zordius@yahoo-inc.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "4.0.17"
+ },
+ "autoload": {
+ "classmap": ["src/lightncandy.php"]
+ }
+}
diff --git a/vendor/zordius/lightncandy/example_debug.png b/vendor/zordius/lightncandy/example_debug.png
new file mode 100644
index 00000000..b117ee87
--- /dev/null
+++ b/vendor/zordius/lightncandy/example_debug.png
Binary files differ
diff --git a/vendor/zordius/lightncandy/phpunit.xml b/vendor/zordius/lightncandy/phpunit.xml
new file mode 100644
index 00000000..4e9417f1
--- /dev/null
+++ b/vendor/zordius/lightncandy/phpunit.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<phpunit
+ backupGlobals="false"
+ colors="true"
+ convertNoticesToExceptions="false"
+>
+ <filter>
+ <whitelist>
+ <directory suffix=".php">./src</directory>
+ </whitelist>
+ </filter>
+
+ <logging>
+ <log type="coverage-html" target="build/result/coverage" title="lightncandy" charset="UTF-8" />
+ <log type="coverage-text" target="php://stdout" />
+ </logging>
+
+ <testsuites>
+ <testsuite name="LightnCandy Test Suite">
+ <directory>./tests/</directory>
+ </testsuite>
+ </testsuites>
+</phpunit>
diff --git a/vendor/zordius/lightncandy/src/lightncandy.php b/vendor/zordius/lightncandy/src/lightncandy.php
new file mode 100644
index 00000000..d7a62e57
--- /dev/null
+++ b/vendor/zordius/lightncandy/src/lightncandy.php
@@ -0,0 +1,2560 @@
+<?php
+/*
+
+Copyrights for code authored by Yahoo! Inc. is licensed under the following terms:
+MIT License
+Copyright (c) 2013 Yahoo! Inc. All Rights Reserved.
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Origin: https://github.com/zordius/lightncandy
+*/
+
+/**
+ * This is abstract engine which defines must-have methods.
+ *
+ * @package LightnCandy
+ * @author Zordius <zordius@yahoo-inc.com>
+ */
+
+/**
+ * LightnCandy static core class.
+ */
+class LightnCandy {
+ // Compile time error handling flags
+ const FLAG_ERROR_LOG = 1;
+ const FLAG_ERROR_EXCEPTION = 2;
+ const FLAG_ERROR_SKIPPARTIAL = 4194304;
+
+ // Compile the template as standalone PHP code which can execute without including LightnCandy
+ const FLAG_STANDALONE = 4;
+ const FLAG_NOESCAPE = 67108864;
+
+ // JavaScript compatibility
+ const FLAG_JSTRUE = 8;
+ const FLAG_JSOBJECT = 16;
+
+ // Handlebars.js compatibility
+ const FLAG_THIS = 32;
+ const FLAG_WITH = 64;
+ const FLAG_PARENT = 128;
+ const FLAG_JSQUOTE = 256;
+ const FLAG_ADVARNAME = 512;
+ const FLAG_SPACECTL = 1024;
+ const FLAG_NAMEDARG = 2048;
+ const FLAG_SPVARS = 4096;
+ const FLAG_SLASH = 8388608;
+ const FLAG_ELSE = 16777216;
+
+ // PHP behavior flags
+ const FLAG_EXTHELPER = 8192;
+ const FLAG_ECHO = 16384;
+ const FLAG_PROPERTY = 32768;
+ const FLAG_METHOD = 65536;
+ const FLAG_RUNTIMEPARTIAL = 1048576;
+
+ // Mustache compatibility
+ const FLAG_MUSTACHESP = 131072;
+ const FLAG_MUSTACHELOOKUP = 262144;
+ const FLAG_MUSTACHEPAIN = 2097152;
+ const FLAG_MUSTACHESEC = 33554432;
+
+ // Template rendering time debug flags
+ const FLAG_RENDER_DEBUG = 524288;
+
+ // alias flags
+ const FLAG_BESTPERFORMANCE = 16384; // FLAG_ECHO
+ const FLAG_JS = 24; // FLAG_JSTRUE + FLAG_JSOBJECT
+ const FLAG_MUSTACHE = 40239104; // FLAG_ERROR_SKIPPARTIAL + FLAG_MUSTACHESP + FLAG_MUSTACHELOOKUP + FLAG_MUSTACHEPAIN + FLAG_MUSTACHESEC
+ const FLAG_HANDLEBARS = 27402208; // FLAG_THIS + FLAG_WITH + FLAG_PARENT + FLAG_JSQUOTE + FLAG_ADVARNAME + FLAG_SPACECTL + FLAG_NAMEDARG + FLAG_SPVARS + FLAG_SLASH + FLAG_ELSE + FLAG_MUSTACHESP + FLAG_MUSTACHEPAIN
+ const FLAG_HANDLEBARSJS = 27402232; // FLAG_JS + FLAG_HANDLEBARS
+ const FLAG_INSTANCE = 98304; // FLAG_PROPERTY + FLAG_METHOD
+
+ // RegExps
+ const VARNAME_SEARCH = '/(\\[[^\\]]+\\]|[^\\[\\]\\.]+)/';
+ const EXTENDED_COMMENT_SEARCH = '/{{!--.*?--}}/s';
+
+ // Positions of matched token
+ const POS_LOTHER = 1;
+ const POS_LSPACE = 2;
+ const POS_BEGINTAG = 3;
+ const POS_LSPACECTL = 4;
+ const POS_OP = 5;
+ const POS_INNERTAG = 6;
+ const POS_RSPACECTL = 7;
+ const POS_ENDTAG = 8;
+ const POS_RSPACE = 9;
+ const POS_ROTHER = 10;
+
+ protected static $lastContext;
+
+ /**
+ * Compile handlebars template into PHP code.
+ *
+ * @param string $template handlebars template string
+ * @param array<string,array|string|integer> $options LightnCandy compile time and run time options, default is array('flags' => LightnCandy::FLAG_BESTPERFORMANCE)
+ *
+ * @return string|false Compiled PHP code when successed. If error happened and compile failed, return false.
+ */
+ public static function compile($template, $options = array('flags' => self::FLAG_BESTPERFORMANCE)) {
+ $context = static::buildContext($options);
+
+ if (static::handleError($context)) {
+ return false;
+ }
+
+ // Strip extended comments
+ $template = preg_replace(static::EXTENDED_COMMENT_SEARCH, '{{!*}}', $template);
+
+ // Do first time scan to find out used feature, detect template error.
+ static::setupToken($context);
+ static::verifyTemplate($context, $template);
+
+ if (static::handleError($context)) {
+ return false;
+ }
+
+ // Do PHP code generation.
+ static::setupToken($context);
+ $code = static::compileTemplate($context, static::escapeTemplate($template));
+
+ // return false when fatal error
+ if (static::handleError($context)) {
+ return false;
+ }
+
+ // Or, return full PHP render codes as string
+ return static::composePHPRender($context, $code);
+ }
+
+ /*
+ * Escape template
+ *
+ * @param string $template handlebars template string
+ *
+ * @return string Escaped template
+ *
+ * @expect 'abc' when input 'abc'
+ * @expect 'a\\bc' when input 'a\bc'
+ * @expect 'a\\\'bc' when input 'a\'bc'
+ */
+ protected static function escapeTemplate($template) {
+ return addcslashes(addcslashes($template, '\\'), "'");
+ }
+
+ /**
+ * Setup token delimiter by default or provided string
+ *
+ * @param array<string,array|string|integer> $context Current context
+ * @param string $left left string of a token
+ * @param string $right right string of a token
+ */
+ protected static function setupToken(&$context, $left = '{{', $right = '}}') {
+ if (preg_match('/=/', "$left$right")) {
+ $context['error'][] = "Can not set delimiter contains '=' , you try to set delimiter as '$left' and '$right'.";
+ return;
+ }
+
+ $context['tokens']['startchar'] = substr($left, 0, 1);
+ $context['tokens']['left'] = $left;
+ $context['tokens']['right'] = $right;
+
+ if (($left === '{{') && ($right === '}}')) {
+ $left = '\\{{2,3}';
+ $right = '\\}{2,3}';
+ } else {
+ $left = preg_quote($left);
+ $right = preg_quote($right);
+ }
+
+ $context['tokens']['search'] = "/^(.*?)(\\s*)($left)(~?)([\\^#\\/!&>]?)(.*?)(~?)($right)(\\s*)(.*)\$/s";
+ }
+
+ /**
+ * Verify template and scan for used features
+ *
+ * @param array<string,array|string|integer> $context Current context
+ * @param string $template handlebars template
+ */
+ protected static function verifyTemplate(&$context, $template) {
+ while (preg_match($context['tokens']['search'], $template, $matches)) {
+ $context['tokens']['count']++;
+ static::scanFeatures($matches, $context);
+ $template = $matches[self::POS_ROTHER];
+ }
+ }
+
+ /**
+ * Compile template into PHP code (internal method)
+ *
+ * @param array<string,array|string|integer> $context Current context
+ * @param string $template handlebars template
+ * @param string $partial partial name when $template is come from the template
+ *
+ * @return string generated PHP code
+ */
+ protected static function compileTemplate(&$context, $template, $partial = '') {
+ // Check for recursive partial
+ if ($partial && !$context['flags']['runpart']) {
+ $context['partialStack'][] = $partial;
+ $diff = count($context['partialStack']) - count(array_unique($context['partialStack']));
+ if ($diff > 1) {
+ $context['error'][] = "Skip rendering partial '$partial' again due to recursive detected";
+ return '';
+ }
+ if ($diff) {
+ $context['error'][] = 'I found recursive partial includes as the path: ' . implode(' -> ', $context['partialStack']) . '! You should fix your template or compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag.';
+ }
+ }
+
+ $code = '';
+ while (preg_match($context['tokens']['search'], $template, $matches)) {
+ // Skip a token when it is slash escaped
+ if ($context['flags']['slash'] && ($matches[self::POS_LSPACE] === '') && preg_match('/^(.*?)(\\\\+)$/s', $matches[self::POS_LOTHER], $escmatch)) {
+ if (strlen($escmatch[2]) % 4) {
+ $code .= substr($matches[self::POS_LOTHER], 0, -2) . $context['tokens']['startchar'];
+ $matches[self::POS_BEGINTAG] = substr($matches[self::POS_BEGINTAG], 1);
+ $template = implode('', array_slice($matches, self::POS_BEGINTAG));
+ continue;
+ } else {
+ $matches[self::POS_LOTHER] = $escmatch[1] . str_repeat('\\', strlen($escmatch[2]) / 2);
+ }
+ }
+
+ $context['tokens']['current']++;
+ $tmpl = static::compileToken($matches, $context);
+ if ($tmpl == $context['ops']['seperator']) {
+ $tmpl = '';
+ } else {
+ $tmpl = "'$tmpl'";
+ }
+ $code .= "{$matches[self::POS_LOTHER]}{$matches[self::POS_LSPACE]}$tmpl";
+ $template = "{$matches[self::POS_RSPACE]}{$matches[self::POS_ROTHER]}";
+ }
+
+ if ($partial && !$context['flags']['runpart']) {
+ array_pop($context['partialStack']);
+ }
+
+ return "$code$template";
+ }
+
+ /**
+ * Compose LightnCandy render codes for include()
+ *
+ * @param array<string,array|string|integer> $context Current context
+ * @param string $code generated PHP code
+ *
+ * @return string Composed PHP code
+ */
+ protected static function composePHPRender($context, $code) {
+ $flagJStrue = static::getBoolStr($context['flags']['jstrue']);
+ $flagJSObj = static::getBoolStr($context['flags']['jsobj']);
+ $flagSPVar = static::getBoolStr($context['flags']['spvar']);
+ $flagProp = static::getBoolStr($context['flags']['prop']);
+ $flagMethod = static::getBoolStr($context['flags']['method']);
+ $flagMustlok = static::getBoolStr($context['flags']['mustlok']);
+ $flagMustsec = static::getBoolStr($context['flags']['mustsec']);
+ $flagEcho = static::getBoolStr($context['flags']['echo']);
+
+ $libstr = static::exportLCRun($context);
+ $constants = static::exportLCRunConstant($context);
+ $helpers = static::exportHelper($context);
+ $bhelpers = static::exportHelper($context, 'blockhelpers');
+ $hbhelpers = static::exportHelper($context, 'hbhelpers');
+ $debug = LCRun3::DEBUG_ERROR_LOG;
+
+ // Return generated PHP code string.
+ return "<?php return function (\$in, \$debugopt = $debug) {
+ \$cx = array(
+ 'flags' => array(
+ 'jstrue' => $flagJStrue,
+ 'jsobj' => $flagJSObj,
+ 'spvar' => $flagSPVar,
+ 'prop' => $flagProp,
+ 'method' => $flagMethod,
+ 'mustlok' => $flagMustlok,
+ 'mustsec' => $flagMustsec,
+ 'echo' => $flagEcho,
+ 'debug' => \$debugopt,
+ ),
+ 'constants' => $constants,
+ 'helpers' => $helpers,
+ 'blockhelpers' => $bhelpers,
+ 'hbhelpers' => $hbhelpers,
+ 'partials' => array({$context['partialCode']}),
+ 'scopes' => array(\$in),
+ 'sp_vars' => array('root' => \$in),
+$libstr
+ );
+ {$context['renderex']}
+ {$context['ops']['op_start']}'$code'{$context['ops']['op_end']}
+}
+?>";
+ }
+
+ /**
+ * Build context from options
+ *
+ * @param array<string,array|string|integer> $options input options
+ *
+ * @return array<string,array|string|integer> Context from options
+ */
+ protected static function buildContext($options) {
+ if (!is_array($options)) {
+ $options = array();
+ }
+
+ $flags = isset($options['flags']) ? $options['flags'] : self::FLAG_BESTPERFORMANCE;
+
+ $context = array(
+ 'flags' => array(
+ 'errorlog' => $flags & self::FLAG_ERROR_LOG,
+ 'exception' => $flags & self::FLAG_ERROR_EXCEPTION,
+ 'skippartial' => $flags & self::FLAG_ERROR_SKIPPARTIAL,
+ 'standalone' => $flags & self::FLAG_STANDALONE,
+ 'noesc' => $flags & self::FLAG_NOESCAPE,
+ 'jstrue' => $flags & self::FLAG_JSTRUE,
+ 'jsobj' => $flags & self::FLAG_JSOBJECT,
+ 'jsquote' => $flags & self::FLAG_JSQUOTE,
+ 'this' => $flags & self::FLAG_THIS,
+ 'with' => $flags & self::FLAG_WITH,
+ 'parent' => $flags & self::FLAG_PARENT,
+ 'echo' => $flags & self::FLAG_ECHO,
+ 'advar' => $flags & self::FLAG_ADVARNAME,
+ 'namev' => $flags & self::FLAG_NAMEDARG,
+ 'spvar' => $flags & self::FLAG_SPVARS,
+ 'slash' => $flags & self::FLAG_SLASH,
+ 'else' => $flags & self::FLAG_ELSE,
+ 'exhlp' => $flags & self::FLAG_EXTHELPER,
+ 'mustsp' => $flags & self::FLAG_MUSTACHESP,
+ 'mustlok' => $flags & self::FLAG_MUSTACHELOOKUP,
+ 'mustpi' => $flags & self::FLAG_MUSTACHEPAIN,
+ 'mustsec' => $flags & self::FLAG_MUSTACHESEC,
+ 'debug' => $flags & self::FLAG_RENDER_DEBUG,
+ 'prop' => $flags & self::FLAG_PROPERTY,
+ 'method' => $flags & self::FLAG_METHOD,
+ 'runpart' => $flags & self::FLAG_RUNTIMEPARTIAL,
+ ),
+ 'level' => 0,
+ 'stack' => array(),
+ 'error' => array(),
+ 'basedir' => static::buildCXBasedir($options),
+ 'fileext' => static::buildCXFileext($options),
+ 'tokens' => array(
+ 'standalone' => true,
+ 'ahead' => false,
+ 'current' => 0,
+ 'count' => 0,
+ 'partialind' => '',
+ ),
+ 'usedPartial' => array(),
+ 'partialStack' => array(),
+ 'partialCode' => '',
+ 'usedFeature' => array(
+ 'rootthis' => 0,
+ 'enc' => 0,
+ 'raw' => 0,
+ 'sec' => 0,
+ 'isec' => 0,
+ 'if' => 0,
+ 'else' => 0,
+ 'unless' => 0,
+ 'each' => 0,
+ 'this' => 0,
+ 'parent' => 0,
+ 'with' => 0,
+ 'dot' => 0,
+ 'comment' => 0,
+ 'partial' => 0,
+ 'helper' => 0,
+ 'bhelper' => 0,
+ 'hbhelper' => 0,
+ 'delimiter' => 0,
+ ),
+ 'usedCount' => array(
+ 'var' => array(),
+ 'helpers' => array(),
+ 'blockhelpers' => array(),
+ 'hbhelpers' => array(),
+ 'lcrun' => array(),
+ ),
+ 'partials' => (isset($options['partials']) && is_array($options['partials'])) ? $options['partials'] : array(),
+ 'helpers' => array(),
+ 'blockhelpers' => array(),
+ 'hbhelpers' => array(),
+ 'renderex' => isset($options['renderex']) ? $options['renderex'] : '',
+ );
+
+ $context['ops'] = $context['flags']['echo'] ? array(
+ 'seperator' => ',',
+ 'f_start' => 'echo ',
+ 'f_end' => ';',
+ 'op_start' => 'ob_start();echo ',
+ 'op_end' => ';return ob_get_clean();',
+ 'cnd_start' => ';if ',
+ 'cnd_then' => '{echo ',
+ 'cnd_else' => ';}else{echo ',
+ 'cnd_end' => ';}echo ',
+ ) : array(
+ 'seperator' => '.',
+ 'f_start' => 'return ',
+ 'f_end' => ';',
+ 'op_start' => 'return ',
+ 'op_end' => ';',
+ 'cnd_start' => '.(',
+ 'cnd_then' => ' ? ',
+ 'cnd_else' => ' : ',
+ 'cnd_end' => ').',
+ );
+
+ $context['ops']['enc'] = $context['flags']['jsquote'] ? 'encq' : 'enc';
+ $context = static::buildHelperTable($context, $options);
+ $context = static::buildHelperTable($context, $options, 'blockhelpers');
+ $context = static::buildHelperTable($context, $options, 'hbhelpers');
+
+ return $context;
+ }
+
+ /**
+ * Build custom helper table
+ *
+ * @param array<string,array|string|integer> $context prepared context
+ * @param array<string,array|string|integer> $options input options
+ * @param string $tname helper table name
+ *
+ * @return array<string,array|string|integer> context with generated helper table
+ *
+ * @expect array() when input array(), array()
+ * @expect array('flags' => array('exhlp' => 1)) when input array('flags' => array('exhlp' => 1)), array('helpers' => array('abc'))
+ * @expect array('error' => array('Can not find custom helper function defination abc() !'), 'flags' => array('exhlp' => 0)) when input array('error' => array(), 'flags' => array('exhlp' => 0)), array('helpers' => array('abc'))
+ * @expect array('flags' => array('exhlp' => 1), 'helpers' => array('LCRun3::raw' => 'LCRun3::raw')) when input array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('LCRun3::raw'))
+ * @expect array('flags' => array('exhlp' => 1), 'helpers' => array('test' => 'LCRun3::raw')) when input array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('test' => 'LCRun3::raw'))
+ */
+ protected static function buildHelperTable($context, $options, $tname = 'helpers') {
+ if (isset($options[$tname]) && is_array($options[$tname])) {
+ foreach ($options[$tname] as $name => $func) {
+ if (is_callable($func)) {
+ $context[$tname][is_int($name) ? $func : $name] = $func;
+ } else {
+ if (is_array($func)) {
+ $context['error'][] = "I found an array in $tname with key as $name, please fix it.";
+ } else {
+ if (!$context['flags']['exhlp']) {
+ $context['error'][] = "Can not find custom helper function defination $func() !";
+ }
+ }
+ }
+ }
+ }
+ return $context;
+ }
+
+ /**
+ * Read partial file content as string and store in context
+ *
+ * @param string $name partial name
+ * @param array<string,array|string|integer> $context Current context of compiler progress.
+ */
+ protected static function readPartial($name, &$context) {
+ $context['usedFeature']['partial']++;
+
+ if (isset($context['usedPartial'][$name])) {
+ return;
+ }
+
+ $cnt = static::resolvePartial($name, $context);
+
+ if ($cnt !== null) {
+ return static::compilePartial($name, $context, $cnt);
+ }
+
+ if (!$context['flags']['skippartial']) {
+ $context['error'][] = "Can not find partial file for '$name', you should set correct basedir and fileext in options";
+ }
+ }
+
+ /**
+ * locate partial file, return the file name
+ *
+ * @param string $name partial name
+ * @param array<string,array|string|integer> $context Current context of compiler progress.
+ *
+ * @return string|null $content partial content
+ */
+ protected static function resolvePartial(&$name, &$context) {
+ if (isset($context['partials'][$name])) {
+ return $context['partials'][$name];
+ }
+
+ foreach ($context['basedir'] as $dir) {
+ foreach ($context['fileext'] as $ext) {
+ $fn = "$dir/$name$ext";
+ if (file_exists($fn)) {
+ return file_get_contents($fn);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * compile partial file, stored in context
+ *
+ * @param string $name partial name
+ * @param array<string,array|string|integer> $context Current context of compiler progress.
+ * @param string $content partial content
+ */
+ protected static function compilePartial(&$name, &$context, $content) {
+ $context['usedPartial'][$name] = static::escapeTemplate($content);
+
+ $originalAhead = $context['tokens']['ahead'];
+ $tmpContext = $context;
+ $tmpContext['level'] = 0;
+ static::setupToken($tmpContext);
+
+ static::verifyTemplate($tmpContext, $content);
+ $originalToken = $context['tokens'];
+ $context = $tmpContext;
+ $context['tokens'] = $originalToken;
+ $context['tokens']['ahead'] = $originalAhead;
+
+ if ($context['flags']['runpart']) {
+ $code = static::compileTemplate($context, $context['usedPartial'][$name], $name);
+ if ($context['flags']['mustpi']) {
+ $sp = ', $sp';
+ $code = preg_replace('/^/m', "'{$context['ops']['seperator']}\$sp{$context['ops']['seperator']}'", $code);
+ } else {
+ $sp = '';
+ }
+ $context['partialCode'] .= "'$name' => function (\$cx, \$in{$sp}) {{$context['ops']['op_start']}'$code'{$context['ops']['op_end']}},";
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Check options and handle fileext.
+ *
+ * @param array<string,array|string|integer> $options current compile option
+ *
+ * @return array<string> file extensions
+ *
+ * @expect array('.tmpl') when input array()
+ * @expect array('test') when input array('fileext' => 'test')
+ * @expect array('test1') when input array('fileext' => array('test1'))
+ * @expect array('test2', 'test3') when input array('fileext' => array('test2', 'test3'))
+ */
+ protected static function buildCXFileext($options) {
+ $exts = isset($options['fileext']) ? $options['fileext'] : '.tmpl';
+ return is_array($exts) ? $exts : array($exts);
+ }
+
+ /**
+ * Internal method used by compile(). Check options and handle basedir.
+ *
+ * @param array<string,array|string|integer> $options current compile option
+ *
+ * @return array<string> base directories
+ *
+ * @expect array() when input array()
+ * @expect array() when input array('basedir' => array())
+ * @expect array('src') when input array('basedir' => array('src'))
+ * @expect array('src') when input array('basedir' => array('src', 'dir_not_found'))
+ * @expect array('src', 'tests') when input array('basedir' => array('src', 'tests'))
+ */
+ protected static function buildCXBasedir($options) {
+ $dirs = isset($options['basedir']) ? $options['basedir'] : 0;
+ $dirs = is_array($dirs) ? $dirs : array($dirs);
+ $ret = array();
+
+ foreach ($dirs as $dir) {
+ if (is_string($dir) && is_dir($dir)) {
+ $ret[] = $dir;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Internal method used by compile(). Get PHP code from a closure of function as string.
+ *
+ * @param object $closure Closure object
+ *
+ * @return string
+ *
+ * @expect 'function($a) {return;}' when input function ($a) {return;}
+ * @expect 'function($a) {return;}' when input function ($a) {return;}
+ */
+ protected static function getPHPCode($closure) {
+ if (is_string($closure) && preg_match('/(.+)::(.+)/', $closure, $matched)) {
+ $ref = new ReflectionMethod($matched[1], $matched[2]);
+ } else {
+ $ref = new ReflectionFunction($closure);
+ }
+ $fname = $ref->getFileName();
+
+ $lines = file_get_contents($fname);
+ $file = new SplFileObject($fname);
+ $file->seek($ref->getStartLine() - 2);
+ $spos = $file->ftell();
+ $file->seek($ref->getEndLine() - 1);
+ $epos = $file->ftell();
+
+ return preg_replace('/^.*?function(\s+[^\s\\(]+?)?\s*?\\((.+?)\\}[,\\s]*;?$/s', 'function($2}', substr($lines, $spos, $epos - $spos));
+ }
+
+ /**
+ * Internal method used by compile(). Export required custom helper functions.
+ *
+ * @param string $tname helper table name
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return string
+ */
+ protected static function exportHelper($context, $tname = 'helpers') {
+ $ret = '';
+ foreach ($context[$tname] as $name => $func) {
+ if (!isset($context['usedCount'][$tname][$name])) {
+ continue;
+ }
+ if ((is_object($func) && ($func instanceof Closure)) || ($context['flags']['exhlp'] == 0)) {
+ $ret .= (" '$name' => " . static::getPHPCode($func) . ",\n");
+ continue;
+ }
+ $ret .= " '$name' => '$func',\n";
+ }
+
+ return "array($ret)";
+ }
+
+ /**
+ * Internal method used by compile(). Export required standalone functions.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return string
+ */
+ protected static function exportLCRun($context) {
+ if ($context['flags']['standalone'] == 0) {
+ return '';
+ }
+
+ $class = new ReflectionClass('LCRun3');
+ $fname = $class->getFileName();
+ $lines = file_get_contents($fname);
+ $file = new SplFileObject($fname);
+ $methods = array();
+ $ret = "'funcs' => array(\n";
+
+ foreach ($class->getMethods() as $method) {
+ $name = $method->getName();
+ $file->seek($method->getStartLine() - 2);
+ $spos = $file->ftell();
+ $file->seek($method->getEndLine() - 2);
+ $epos = $file->ftell();
+ $methods[$name] = static::scanLCRunDependency($context, preg_replace('/public static function (.+)\\(/', '\'$1\' => function (', substr($lines, $spos, $epos - $spos)));
+ }
+ unset($file);
+
+ $exports = array_keys($context['usedCount']['lcrun']);
+
+ while (true) {
+ if (array_sum(array_map(function ($name) use (&$exports, $methods) {
+ $n = 0;
+ foreach ($methods[$name][1] as $child => $count) {
+ if (!in_array($child, $exports)) {
+ $exports[] = $child;
+ $n++;
+ }
+ }
+ return $n;
+ }, $exports)) == 0) {
+ break;
+ }
+ }
+
+ foreach ($exports as $export) {
+ $ret .= ($methods[$export][0] . " },\n");
+ }
+
+ return "$ret)\n";
+ }
+
+ /**
+ * Internal method used by compile(). Export standalone constants.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return string
+ */
+ protected static function exportLCRunConstant($context) {
+ if ($context['flags']['standalone'] == 0) {
+ return 'array()';
+ }
+
+ $class = new ReflectionClass('LCRun3');
+ $constants = $class->getConstants();
+ $ret = " array(\n";
+ foreach($constants as $name => $value) {
+ $ret .= " '$name' => ". (is_string($value) ? "'$value'" : $value ) . ",\n";
+ }
+ $ret .= " )";
+ return $ret;
+ }
+
+ /**
+ * Internal method used by compile(). Export required standalone functions.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ * @param string $code PHP code string of the method
+ *
+ * @return array<string|array> list of converted code and children array
+ */
+ protected static function scanLCRunDependency($context, $code) {
+ $child = array();
+
+ $code = preg_replace_callback('/self::(\w+?)\s*\(/', function ($matches) use ($context, &$child) {
+ if (!isset($child[$matches[1]])) {
+ $child[$matches[1]] = 0;
+ }
+ $child[$matches[1]]++;
+
+ return "\$cx['funcs']['{$matches[1]}'](";
+ }, $code);
+
+ // replace the constants
+ $code = preg_replace('/self::([A-Z0-9_]+)/', "\$cx['constants']['$1']", $code);
+ return array($code, $child);
+ }
+
+ /**
+ * Internal method used by compile(). Handle exists error and return error status.
+ *
+ * @param array<string,array|string|integer> $context Current context of compiler progress.
+ *
+ * @throws Exception
+ * @return boolean True when error detected
+ *
+ * @expect true when input array('level' => 1, 'stack' => array('X'), 'flags' => array('errorlog' => 0, 'exception' => 0), 'error' => array())
+ * @expect false when input array('level' => 0, 'error' => array())
+ * @expect true when input array('level' => 0, 'error' => array('some error'), 'flags' => array('errorlog' => 0, 'exception' => 0))
+ */
+ protected static function handleError(&$context) {
+ if ($context['level'] > 0) {
+ $token = array_pop($context['stack']);
+ $context['error'][] = "Unclosed token {{{#$token}}} !!";
+ }
+
+ static::$lastContext = $context;
+
+ if (count($context['error'])) {
+ if ($context['flags']['errorlog']) {
+ error_log(implode("\n", $context['error']));
+ }
+ if ($context['flags']['exception']) {
+ throw new Exception(implode("\n", $context['error']));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Internal method used by compile(). Return 'true' or 'false' string.
+ *
+ * @param integer $v value
+ *
+ * @return string 'true' when the value larger then 0
+ *
+ * @expect 'true' when input 1
+ * @expect 'true' when input 999
+ * @expect 'false' when input 0
+ * @expect 'false' when input -1
+ */
+ protected static function getBoolStr($v) {
+ return ($v > 0) ? 'true' : 'false';
+ }
+
+ /**
+ * Get last compiler context.
+ *
+ * @return array<string,array|string|integer> Context data
+ */
+ public static function getContext() {
+ return static::$lastContext;
+ }
+
+ /**
+ * Get a working render function by a string of PHP code. This method may requires php setting allow_url_include=1 and allow_url_fopen=1 , or access right to tmp file system.
+ *
+ * @param string $php PHP code
+ * @param string|null $tmpDir Optional, change temp directory for php include file saved by prepare() when cannot include PHP code with data:// format.
+ *
+ * @return Closure|false result of include()
+ *
+ * @deprecated
+ */
+ public static function prepare($php, $tmpDir = null) {
+ if (!ini_get('allow_url_include') || !ini_get('allow_url_fopen')) {
+ if (!$tmpDir || !is_dir($tmpDir)) {
+ $tmpDir = sys_get_temp_dir();
+ }
+ }
+
+ if ($tmpDir) {
+ $fn = tempnam($tmpDir, 'lci_');
+ if (!$fn) {
+ error_log("Can not generate tmp file under $tmpDir!!\n");
+ return false;
+ }
+ if (!file_put_contents($fn, $php)) {
+ error_log("Can not include saved temp php code from $fn, you should add $tmpDir into open_basedir!!\n");
+ return false;
+ }
+ return include($fn);
+ }
+
+ return include('data://text/plain,' . urlencode($php));
+ }
+
+ /**
+ * Internal method used by compile(). Get function name for standalone or none standalone template.
+ *
+ * @param array<string,array|string|integer> $context Current context of compiler progress.
+ * @param string $name base function name
+ * @param string $tag original handlabars tag for debug
+ *
+ * @return string compiled Function name
+ *
+ * @expect 'LCRun3::test(' when input array('flags' => array('standalone' => 0, 'debug' => 0)), 'test', ''
+ * @expect 'LCRun3::test2(' when input array('flags' => array('standalone' => 0, 'debug' => 0)), 'test2', ''
+ * @expect "\$cx['funcs']['test3'](" when input array('flags' => array('standalone' => 1, 'debug' => 0)), 'test3', ''
+ * @expect 'LCRun3::debug(\'abc\', \'test\', ' when input array('flags' => array('standalone' => 0, 'debug' => 1)), 'test', 'abc'
+ */
+ protected static function getFuncName(&$context, $name, $tag) {
+ static::addUsageCount($context, 'lcrun', $name);
+
+ if ($context['flags']['debug'] && ($name != 'miss')) {
+ $dbg = "'$tag', '$name', ";
+ $name = 'debug';
+ static::addUsageCount($context, 'lcrun', 'debug');
+ } else {
+ $dbg = '';
+ }
+
+ return $context['flags']['standalone'] ? "\$cx['funcs']['$name']($dbg" : "LCRun3::$name($dbg";
+ }
+
+ /**
+ * Internal method used by getArrayCode(). Get variable names translated string.
+ *
+ * @param array<string> $scopes an array of variable names with single quote
+ *
+ * @return string PHP array names string
+ *
+ * @expect '' when input array()
+ * @expect '[a]' when input array('a')
+ * @expect '[a][b][c]' when input array('a', 'b', 'c')
+ */
+ protected static function getArrayStr($scopes) {
+ return count($scopes) ? '[' . implode('][', $scopes) . ']' : '';
+ }
+
+ /**
+ * Internal method used by getVariableName(). Get variable names translated string.
+ *
+ * @param array<string> $list an array of variable names.
+ *
+ * @return string PHP array names string
+ *
+ * @expect '' when input array()
+ * @expect "['a']" when input array('a')
+ * @expect "['a']['b']['c']" when input array('a', 'b', 'c')
+ */
+ protected static function getArrayCode($list) {
+ return static::getArrayStr(array_map(function ($v) {
+ return "'$v'";
+ }, $list));
+ }
+
+ /**
+ * Internal method used by compile().
+ *
+ * @param array<array> $vn variable name array.
+ * @param array<string,array|string|integer> $context current compile context
+ * @param boolean $ishelper true when compile for helper
+ *
+ * @return array<string|array> variable names
+ *
+ * @expect array('array(array($in),array())', array('this')) when input array(null), array('flags'=>array('spvar'=>true))
+ * @expect array('array(array($in,$in),array())', array('this', 'this')) when input array(null, null), array('flags'=>array('spvar'=>true))
+ * @expect array('array(array(),array(\'a\'=>$in))', array('this')) when input array('a' => null), array('flags'=>array('spvar'=>true))
+ */
+ protected static function getVariableNames($vn, &$context, $ishelper = false) {
+ $vars = array(array(), array());
+ $exps = array();
+ foreach ($vn as $i => $v) {
+ if (isset($v[0]) && preg_match('/^\(.+\)$/', $v[0])) {
+ $V = static::compileSubExpression($v[0], $context);
+ } else {
+ $V = static::getVariableName($v, $context, $ishelper);
+ }
+ if (is_string($i)) {
+ $vars[1][] = "'$i'=>{$V[0]}";
+ } else {
+ $vars[0][] = $V[0];
+ }
+ $exps[] = $V[1];
+ }
+ return array('array(array(' . implode(',', $vars[0]) . '),array(' . implode(',', $vars[1]) . '))', $exps);
+ }
+
+ /**
+ * Internal method used by compile().
+ *
+ * @param string $subExpression subExpression to compile
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return array<string> code representing passed expression
+ */
+ protected static function compileSubExpression($subExpression, &$context) {
+ // mock up a token for this expression
+ $token = array_fill(self::POS_LOTHER, self::POS_ROTHER, '');
+
+ // strip outer ( ) from subexpression
+ $token[self::POS_INNERTAG] = substr($subExpression, 1, -1);
+
+ list(, $vars) = static::parseTokenArgs($token, $context);
+
+ // no separator is needed, this code will be used as a function argument
+ $origSeperator = $context['ops']['seperator'];
+ $context['ops']['seperator'] = '';
+ // override $raw, subexpressions are never escaped
+ $ret = static::compileCustomHelper($context, $vars, true, true);
+ $context['ops']['seperator'] = $origSeperator;
+
+ return array($ret ? $ret : '', $subExpression);
+ }
+
+ /**
+ * Internal method used by compile().
+ *
+ * @param array<array|string|integer> $var variable parsed path
+ * @param array<array|string|integer> $context current compile context
+ * @param boolean $ishelper true when compile for helper$
+ *
+ * @return array<string> variable names
+ *
+ * @expect array('$in', 'this') when input array(null), array('flags'=>array('spvar'=>true,'debug'=>0))
+ * @expect array('true', 'true') when input array('true'), array('flags'=>array('spvar'=>true,'debug'=>0)), true
+ * @expect array('false', 'false') when input array('false'), array('flags'=>array('spvar'=>true,'debug'=>0)), true
+ * @expect array(2, '2') when input array('2'), array('flags'=>array('spvar'=>true,'debug'=>0)), true
+ * @expect array('((isset($in[\'@index\']) && is_array($in)) ? $in[\'@index\'] : null)', '[@index]') when input array('@index'), array('flags'=>array('spvar'=>false,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ * @expect array("((isset(\$cx['sp_vars']['index']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['index'] : null)", '@[index]') when input array('@index'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ * @expect array("((isset(\$cx['sp_vars']['key']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['key'] : null)", '@[key]') when input array('@key'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ * @expect array("((isset(\$cx['sp_vars']['first']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['first'] : null)", '@[first]') when input array('@first'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ * @expect array("((isset(\$cx['sp_vars']['last']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['last'] : null)", '@[last]') when input array('@last'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ * @expect array('\'a\'', '"a"') when input array('"a"'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ * @expect array('((isset($in[\'a\']) && is_array($in)) ? $in[\'a\'] : null)', '[a]') when input array('a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-1])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'] : null)', '../[a]') when input array(1,'a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ * @expect array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-3])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'] : null)', '../../../[a]') when input array(3,'a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ * @expect array('((isset($in[\'id\']) && is_array($in)) ? $in[\'id\'] : null)', 'this.[id]') when input array(null, 'id'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ * @expect array('LCRun3::v($cx, $in, array(\'id\'))', 'this.[id]') when input array(null, 'id'), array('flags'=>array('prop'=>true,'spvar'=>true,'debug'=>0,'method'=>0,'mustlok'=>0,'standalone'=>0))
+ */
+ protected static function getVariableName($var, &$context, $ishelper = false) {
+ if (isset($var[0])) {
+ // Handle language constants or number , only for helpers
+ if ($ishelper) {
+ if ((count($var) == 1) && is_numeric($var[0])) {
+ // convert 0x00 or 0b00 numbers to decimal
+ return array((string) 1 * $var[0], $var[0]);
+ }
+ switch ($var[0]) {
+ case 'true':
+ return array('true', 'true');
+ case 'false':
+ return array('false', 'false');
+ }
+ }
+
+ // Handle double quoted string
+ if (preg_match('/^"(.*)"$/', $var[0], $matched)) {
+ $t = addcslashes(stripslashes(preg_replace('/\\\\\\\\/', '\\', $matched[1])), "'");
+ return array("'{$t}'", "\"{$t}\"");
+ }
+ }
+
+ $levels = 0;
+ $base = '$in';
+ $spvar = false;
+
+ if (isset($var[0])) {
+ // trace to parent
+ if (!is_string($var[0]) && is_int($var[0])) {
+ $levels = array_shift($var);
+ }
+
+ // handle @root, @index, @key, @last, etc
+ if ($context['flags']['spvar']) {
+ if (substr($var[0], 0, 1) === '@') {
+ $spvar = true;
+ $base = "\$cx['sp_vars']";
+ $var[0] = substr($var[0], 1);
+ }
+ }
+
+ // change base when trace to parent
+ if ($levels > 0) {
+ if ($spvar) {
+ $base .= str_repeat("['_parent']", $levels);
+ } else {
+ $base = "\$cx['scopes'][count(\$cx['scopes'])-$levels]";
+ }
+ }
+ }
+
+ // Generate normalized expression for debug
+ $exp = static::getExpression($levels, $spvar, $var);
+
+ if ((count($var) == 0) || (is_null($var[0]) && (count($var) == 1))) {
+ return array($base, $exp);
+ }
+
+ if (is_null($var[0])) {
+ array_shift($var);
+ }
+
+ // 1. To support recursive context lookup...
+ // 2. To support instance properties or methods...
+ // the only way is using slower rendering time variable resolver.
+ if ($context['flags']['prop'] || $context['flags']['method'] || $context['flags']['mustlok']) {
+ return array(static::getFuncName($context, 'v', $exp) . "\$cx, $base, array(" . implode(',', array_map(function ($V) {
+ return "'$V'";
+ }, $var)) . '))', $exp);
+ }
+
+ $n = static::getArrayCode($var);
+ array_pop($var);
+ $p = count($var) ? static::getArrayCode($var) : '';
+
+ return array("((isset($base$n) && is_array($base$p)) ? $base$n : " . ($context['flags']['debug'] ? (static::getFuncName($context, 'miss', '') . "\$cx, '$exp')") : 'null' ) . ')', $exp);
+ }
+
+ /**
+ * Internal method used by compile().
+ *
+ * @param integer $levels trace N levels top parent scope
+ * @param boolean $spvar is the path start with @ or not
+ * @param array<string|integer> $var variable parsed path
+ *
+ * @return string normalized expression for debug display
+ *
+ * @expect '[a].[b]' when input 0, false, array('a', 'b')
+ * @expect '@[root]' when input 0, true, array('root')
+ * @expect 'this' when input 0, false, null
+ * @expect 'this.[id]' when input 0, false, array(null, 'id')
+ * @expect '@[root].[a].[b]' when input 0, true, array('root', 'a', 'b')
+ * @expect '../../[a].[b]' when input 2, false, array('a', 'b')
+ * @expect '../[a\'b]' when input 1, false, array('a\'b')
+ */
+ protected static function getExpression($levels, $spvar, $var) {
+ return ($spvar ? '@' : '') . str_repeat('../', $levels) . ((is_array($var) && count($var)) ? implode('.', array_map(function($v) {
+ return is_null($v) ? 'this' : "[$v]";
+ }, $var)) : 'this');
+ }
+
+ /**
+ * Internal method used by compile(). Return array presentation for a variable name
+ *
+ * @param string $v variable name to be fixed.
+ * @param array<string,array|string|integer> $context Current compile content.
+ *
+ * @return array<integer,string> Return variable name array
+ *
+ * @expect array('this') when input 'this', array('flags' => array('advar' => 0, 'this' => 0))
+ * @expect array(null) when input 'this', array('flags' => array('advar' => 0, 'this' => 1))
+ * @expect array(1, null) when input '../', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array(1, null) when input '../.', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array(1, null) when input '../this', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array(1, 'a') when input '../a', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array(2, 'a', 'b') when input '../../a.b', array('flags' => array('advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array(2, '[a]', 'b') when input '../../[a].b', array('flags' => array('advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array(2, 'a', 'b') when input '../../[a].b', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array('"a.b"') when input '"a.b"', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ * @expect array(null, 'id') when input 'this.id', array('flags' => array('advar' => 1, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ */
+ protected static function fixVariable($v, &$context) {
+ $ret = array();
+ $levels = 0;
+
+ // handle double quoted string
+ if (preg_match('/^"(.*)"$/', $v, $matched)) {
+ return array($v);
+ }
+
+ // handle single quoted string
+ if (preg_match('/^\\\\\'(.*)\\\\\'$/', $v, $matched)) {
+ return array("\"{$matched[1]}\"");
+ }
+
+ // handle ..
+ if ($v === '..') {
+ $v = '../';
+ }
+
+ // Trace to parent for ../ N times
+ $v = preg_replace_callback('/\\.\\.\\//', function() use (&$levels) {
+ $levels++;
+ return '';
+ }, trim($v));
+
+ if ($levels) {
+ $ret[] = $levels;
+ if (!$context['flags']['parent']) {
+ $context['error'][] = 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag';
+ }
+ $context['usedFeature']['parent']++;
+ }
+
+ if ($context['flags']['advar'] && preg_match('/\\]/', $v)) {
+ preg_match_all(self::VARNAME_SEARCH, $v, $matchedall);
+ } else {
+ preg_match_all('/([^\\.\\/]+)/', $v, $matchedall);
+ }
+
+ if (($v === '.') || ($v === '')) {
+ $matchedall = array(array('.'), array('.'));
+ }
+
+ foreach ($matchedall[1] as $m) {
+ if ($context['flags']['advar'] && substr($m, 0, 1) === '[') {
+ $ret[] = substr($m, 1, -1);
+ } else {
+ $ret[] = (($context['flags']['this'] && ($m === 'this')) || ($m === '.')) ? null : $m;
+ }
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Internal method used by scanFeatures() and compile(). Parse the token and return parsed result.
+ *
+ * @param array<string> $token preg_match results
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return array<boolean|array> Return parsed result
+ *
+ * @expect array(false, array(array(null))) when input array(0,0,0,0,0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(true, array(array(null))) when input array(0,0,0,'{{{',0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(true, array(array(null))) when input array(0,0,0,0,0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 1))
+ * @expect array(false, array(array('a'))) when input array(0,0,0,0,0,0,'a'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('b'))) when input array(0,0,0,0,0,0,'a b'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('"b'), array('c"'))) when input array(0,0,0,0,0,0,'a "b c"'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('"b c"'))) when input array(0,0,0,0,0,0,'a "b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('[b'), array('c]'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('b c'))) when input array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('a'), 'q' => array('b c'))) when input array(0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('a'), array('q=[b c'))) when input array(0,0,0,0,0,0,'a [q=[b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('a'), 'q' => array('[b'), array('c]'))) when input array(0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('a'), 'q' => array('b'), array('c'))) when input array(0,0,0,0,0,0,'a [q]=b c'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('a'), 'q' => array('"b c"'))) when input array(0,0,0,0,0,0,'a q="b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('(foo bar)'))) when input array(0,0,0,0,0,0,'(foo bar)'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ * @expect array(false, array(array('foo'), array("'=='"), array('bar'))) when input array(0,0,0,0,0,0,"foo '==' bar"), array('flags' => array('advar' => 1, 'namev' => 1))
+ */
+ protected static function parseTokenArgs(&$token, &$context) {
+ trim($token[self::POS_INNERTAG]);
+
+ // Handle delimiter change
+ if (preg_match('/^=\s*([^ ]+)\s+([^ ]+)\s*=$/', $token[self::POS_INNERTAG], $matched)) {
+ static::setupToken($context, $matched[1], $matched[2]);
+ $token[self::POS_OP] = ' ';
+ return array(false, array());
+ }
+
+ $vars = array();
+ $count = preg_match_all('/(\s*)([^\s]+)/', $token[self::POS_INNERTAG], $matchedall);
+
+ // Parse arguments and deal with "..." or [...] or (...)
+ if (($count > 0) && $context['flags']['advar']) {
+ $prev = '';
+ $expect = 0;
+ foreach ($matchedall[2] as $index => $t) {
+ // continue from previous match when expect something
+ if ($expect) {
+ $prev .= "{$matchedall[1][$index]}$t";
+ // end an argument when end with expected charactor
+ if (substr($t, -1, 1) === $expect) {
+ $vars[] = $prev;
+ $prev = '';
+ $expect = 0;
+ }
+ continue;
+ }
+
+ // continue to next match when begin with '(' without ending ')'
+ if (preg_match('/^\([^\)]+$/', $t)) {
+ $prev = $t;
+ $expect = ')';
+ continue;
+ }
+
+ // continue to next match when begin with '"' without ending '"'
+ if (preg_match('/^"[^"]+$/', $t)) {
+ $prev = $t;
+ $expect = '"';
+ continue;
+ }
+
+ // continue to next match when '="' exists without ending '"'
+ if (preg_match('/="[^"]+$/', $t)) {
+ $prev = $t;
+ $expect = '"';
+ continue;
+ }
+
+ // continue to next match when begin with \' without ending '
+ if (preg_match('/^\\\\\'[^\']+$/', $t)) {
+ $prev = $t;
+ $expect = '\'';
+ continue;
+ }
+
+ // continue to next match when =\' exists without ending '
+ if (preg_match('/=\\\\\'[^\']+$/', $t)) {
+ $prev = $t;
+ $expect = '\'';
+ continue;
+ }
+
+ // continue to next match when '[' exists without ending ']'
+ if (preg_match('/\\[[^\\]]+$/', $t)) {
+ $prev = $t;
+ $expect = ']';
+ continue;
+ }
+ $vars[] = $t;
+ }
+ } else {
+ $vars = ($count > 0) ? $matchedall[2] : explode(' ', $token[self::POS_INNERTAG]);
+ }
+
+ // Check for advanced variable.
+ $ret = array();
+ $i = 0;
+ foreach ($vars as $idx => $var) {
+ // Skip advanced processing for subexpressions
+ if (preg_match('/^\(.+\)$/', $var)) {
+ $ret[$i] = array($var);
+ $i++;
+ continue;
+ }
+
+ if ($context['flags']['namev']) {
+ if (preg_match('/^((\\[([^\\]]+)\\])|([^=^["\']+))=(.+)$/', $var, $m)) {
+ if (!$context['flags']['advar'] && $m[3]) {
+ $context['error'][] = "Wrong argument name as '[$m[3]]' in " . static::tokenString($token) . ' ! You should fix your template or compile with LightnCandy::FLAG_ADVARNAME flag.';
+ }
+ $idx = $m[3] ? $m[3] : $m[4];
+ $var = $m[5];
+ }
+ }
+ if ($context['flags']['advar']) {
+ // foo] Rule 1: no starting [ or [ not start from head
+ if (preg_match('/^[^\\[\\.]+[\\]\\[]/', $var)
+ // [bar Rule 2: no ending ] or ] not in the end
+ || preg_match('/[\\[\\]][^\\]\\.]+$/', $var)
+ // ]bar. Rule 3: middle ] not before .
+ || preg_match('/\\][^\\]\\[\\.]+\\./', $var)
+ // .foo[ Rule 4: middle [ not after .
+ || preg_match('/\\.[^\\]\\[\\.]+\\[/', preg_replace('/^(..\\/)+/', '', preg_replace('/\\[[^\\]]+\\]/', '[XXX]', $var)))
+ ) {
+ $context['error'][] = "Wrong variable naming as '$var' in " . static::tokenString($token) . ' !';
+ }
+ }
+
+ if (($idx === 0) && ($token[self::POS_OP] === '>')) {
+ $var = array(preg_replace('/^("(.+)")|(\\[(.+)\\])$/', '$2$4', $var));
+ } else if (is_numeric($var)) {
+ $var = array('"' . $var . '"');
+ } else {
+ $var = static::fixVariable($var, $context);
+ }
+
+ if (is_string($idx)) {
+ $ret[$idx] = $var;
+ } else {
+ $ret[$i] = $var;
+ $i++;
+ }
+ }
+
+ return array(($token[self::POS_BEGINTAG] === '{{{') || ($token[self::POS_OP] === '&') || $context['flags']['noesc'], $ret);
+ }
+
+ /**
+ * Internal method used by scanFeatures(). return token string
+ *
+ * @param string[] $token detected handlebars {{ }} token
+ * @param integer $remove remove how many heading and ending token
+ *
+ * @return string Return whole token
+ *
+ * @expect 'b' when input array(0, 'a', 'b', 'c'), 1
+ * @expect 'c' when input array(0, 'a', 'b', 'c', 'd', 'e')
+ */
+ protected static function tokenString($token, $remove = 2) {
+ return implode('', array_slice($token, 1 + $remove, -$remove));
+ }
+
+ /**
+ * Internal method used by scanFeatures(). Validate start and and.
+ *
+ * @param string[] $token detected handlebars {{ }} token
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return boolean|null Return true when invalid
+ *
+ * @expect null when input array_fill(0, 9, ''), array()
+ * @expect null when input array_fill(0, 9, '}}'), array()
+ * @expect true when input array_fill(0, 9, '{{{'), array()
+ */
+ protected static function validateStartEnd($token, &$context) {
+ // {{ }}} or {{{ }} are invalid
+ if (strlen($token[self::POS_BEGINTAG]) !== strlen($token[self::POS_ENDTAG])) {
+ $context['error'][] = 'Bad token ' . static::tokenString($token) . ' ! Do you mean {{' . static::tokenString($token, 4) . '}} or {{{' . static::tokenString($token, 4) . '}}}?';
+ return true;
+ }
+ // {{{# }}} or {{{! }}} or {{{/ }}} or {{{^ }}} are invalid.
+ if ((strlen($token[self::POS_BEGINTAG]) === 3) && $token[self::POS_OP] && ($token[self::POS_OP] !== '&')) {
+ $context['error'][] = 'Bad token ' . static::tokenString($token) . ' ! Do you mean {{' . static::tokenString($token, 4) . '}} ?';
+ return true;
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Collect handlebars usage information, detect template error.
+ *
+ * @param string[] $token detected handlebars {{ }} token
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array> $vars parsed arguments list
+ *
+ * @return boolean|integer|null Return true when invalid or detected
+ *
+ * @expect null when input array(0, 0, 0, 0, 0, ''), array(), array()
+ * @expect 2 when input array(0, 0, 0, 0, 0, '^', '...'), array('usedFeature' => array('isec' => 1), 'level' => 0), array(array('foo'))
+ * @expect 3 when input array(0, 0, 0, 0, 0, '!', '...'), array('usedFeature' => array('comment' => 2)), array()
+ * @expect true when input array(0, 0, 0, 0, 0, '/'), array('stack' => array(1), 'level' => 1), array()
+ * @expect 4 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('sec' => 3), 'level' => 0), array(array('x'))
+ * @expect 5 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('if' => 4), 'level' => 0), array(array('if'))
+ * @expect 6 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('with' => 5), 'level' => 0, 'flags' => array('with' => 1)), array(array('with'))
+ * @expect 7 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('each' => 6), 'level' => 0), array(array('each'))
+ * @expect 8 when input array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('unless' => 7), 'level' => 0), array(array('unless'))
+ * @expect 9 when input array(0, 0, 0, 0, 0, '#', '...'), array('blockhelpers' => array('abc' => ''), 'usedFeature' => array('bhelper' => 8), 'level' => 0), array(array('abc'))
+ * @expect 10 when input array(0, 0, 0, 0, 0, ' ', '...'), array('usedFeature' => array('delimiter' => 9), 'level' => 0), array()
+ * @expect 11 when input array(0, 0, 0, 0, 0, '#', '...'), array('hbhelpers' => array('abc' => ''), 'usedFeature' => array('hbhelper' => 10), 'level' => 0), array(array('abc'))
+ * @expect true when input array(0, 0, 0, 0, 0, '>', '...'), array('basedir' => array('.'), 'fileext' => array('.tmpl'), 'usedFeature' => array('unless' => 7, 'partial' => 7), 'level' => 0, 'flags' => array('skippartial' => 0)), array('test')
+ */
+ protected static function validateOperations($token, &$context, $vars) {
+ switch ($token[self::POS_OP]) {
+ case '>':
+ static::readPartial($vars[0][0], $context);
+ return true;
+
+ case ' ':
+ return ++$context['usedFeature']['delimiter'];
+
+ case '^':
+ if ($vars[0][0]) {
+ $context['stack'][] = $token[self::POS_INNERTAG];
+ $context['level']++;
+ return ++$context['usedFeature']['isec'];
+ }
+
+ if (!$context['flags']['else']) {
+ $context['error'][] = 'Do not support {{^}}, you should do compile with LightnCandy::FLAG_ELSE flag';
+ }
+ return;
+
+ case '/':
+ array_pop($context['stack']);
+ $context['level']--;
+ return true;
+
+ case '!':
+ return ++$context['usedFeature']['comment'];
+
+ case '#':
+ $context['stack'][] = $token[self::POS_INNERTAG];
+ $context['level']++;
+
+ // detect handlebars custom helpers.
+ if (isset($context['hbhelpers'][$vars[0][0]])) {
+ return ++$context['usedFeature']['hbhelper'];
+ }
+
+ // detect block custom helpers.
+ if (isset($context['blockhelpers'][$vars[0][0]])) {
+ return ++$context['usedFeature']['bhelper'];
+ }
+
+ switch ($vars[0][0]) {
+ case 'with':
+ if ($context['flags']['with']) {
+ if (count($vars) < 2) {
+ $context['error'][] = 'No argument after {{#with}} !';
+ }
+ } else {
+ if (isset($vars[1][0])) {
+ $context['error'][] = 'Do not support {{#with var}}, you should do compile with LightnCandy::FLAG_WITH flag';
+ }
+ }
+ // Continue to add usage...
+ case 'each':
+ case 'unless':
+ case 'if':
+ return ++$context['usedFeature'][$vars[0][0]];
+
+ default:
+ return ++$context['usedFeature']['sec'];
+ }
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Collect handlebars usage information, detect template error.
+ *
+ * @param string[] $token detected handlebars {{ }} token
+ * @param array<string,array|string|integer> $context current compile context
+ */
+ protected static function scanFeatures($token, &$context) {
+ list($raw, $vars) = static::parseTokenArgs($token, $context);
+
+ if (static::validateStartEnd($token, $context)) {
+ return;
+ }
+
+ if (static::validateOperations($token, $context, $vars)) {
+ return;
+ }
+
+ if (($token[self::POS_OP] === '^') && ($context['flags']['else'])) {
+ return $context['usedFeature']['else']++;
+ }
+
+ if (count($vars) == 0) {
+ return $context['error'][] = 'Wrong variable naming in ' . static::tokenString($token);
+ }
+
+ if (!isset($vars[0])) {
+ return static::noNamedArguments($token, $context, true, ', you should use it after a custom helper.');
+ }
+
+ if ($vars[0] !== 'else') {
+ $context['usedFeature'][$raw ? 'raw' : 'enc']++;
+ }
+
+ // validate else and this.
+ switch ($vars[0][0]) {
+ case 'else':
+ if ($context['flags']['else']) {
+ return $context['usedFeature']['else']++;
+ }
+ break;
+
+ case 'this':
+ case '.':
+ if ($context['level'] == 0) {
+ $context['usedFeature']['rootthis']++;
+ }
+ return $context['usedFeature'][($vars[0] == '.') ? 'dot' : 'this']++;
+ }
+
+ // detect handlebars custom helpers.
+ if (isset($context['hbhelpers'][$vars[0][0]])) {
+ return $context['usedFeature']['hbhelper']++;
+ }
+
+ // detect custom helpers.
+ if (isset($context['helpers'][$vars[0][0]])) {
+ return $context['usedFeature']['helper']++;
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Show error message when named arguments appear without custom helper.
+ *
+ * @param array<string> $token detected handlebars {{ }} token
+ * @param array<string,array|string|integer> $context current compile context
+ * @param boolean $named is named arguments
+ * @param string $suggest extended hint for this no named argument error
+ */
+ public static function noNamedArguments($token, &$context, $named, $suggest = '!') {
+ if ($named) {
+ $context['error'][] = 'Do not support name=value in ' . static::tokenString($token) . $suggest;
+ }
+ }
+
+ /**
+ * Internal method used by compileToken(). Modify $token when spacing rules matched.
+ *
+ * @param array<string> $token detected handlebars {{ }} token
+ * @param array<array|string|integer> $vars parsed arguments list
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return string|null Return compiled code segment for the token
+ */
+ public static function handleMustacheSpacing(&$token, $vars, &$context) {
+ if (!$context['flags']['mustsp'] && !$context['flags']['mustpi']) {
+ return;
+ }
+
+ // left line change detection
+ $lsp = preg_match('/^(.*)(\\r?\\n)([ \\t]*?)$/s', $token[self::POS_LSPACE], $lmatch);
+ $ind = $lsp ? $lmatch[3] : $token[self::POS_LSPACE];
+
+ // right line change detection
+ $rsp = preg_match('/^([ \\t]*?)(\\r?\\n)(.*)$/s', $token[self::POS_RSPACE], $rmatch);
+ $st = true;
+
+ // setup ahead flag
+ $ahead = $context['tokens']['ahead'];
+ $context['tokens']['ahead'] = preg_match('/^[^\n]*{{/s', $token[self::POS_RSPACE] . $token[self::POS_ROTHER]);
+
+ // reset partial indent
+ $context['tokens']['partialind'] = '';
+
+ // same tags in the same line , not standalone
+ if (!$lsp && $ahead) {
+ $st = false;
+ }
+
+ // Do not need standalone detection for these tags
+ if (!$token[self::POS_OP] || ($token[self::POS_OP] === '&')) {
+ if (!$context['flags']['else'] || (isset($vars[0][0]) && ($vars[0][0] !== 'else'))) {
+ $st = false;
+ }
+ }
+
+ // not standalone because other things in the same line ahead
+ if ($token[self::POS_LOTHER] && !$token[self::POS_LSPACE]) {
+ $st = false;
+ }
+
+ // not standalone because other things in the same line behind
+ if ($token[self::POS_ROTHER] && !$token[self::POS_RSPACE]) {
+ $st = false;
+ }
+
+ if ($st && (($lsp && $rsp) // both side cr
+ || ($rsp && !$token[self::POS_LOTHER]) // first line without left
+ || ($lsp && ($context['tokens']['current'] == $context['tokens']['count']) && !$token[self::POS_ROTHER]) // final line
+ )) {
+ // handle partial
+ if ($context['flags']['mustpi'] && ($token[self::POS_OP] === '>')) {
+ $context['tokens']['partialind'] = $ind;
+ }
+ if ($context['flags']['mustsp']) {
+ $token[self::POS_LSPACE] = (isset($lmatch[2]) ? ($lmatch[1] . $lmatch[2]) : '');
+ $token[self::POS_RSPACE] = isset($rmatch[3]) ? $rmatch[3] : '';
+ }
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars token.
+ *
+ * @param array<string> $token detected handlebars {{ }} token
+ * @param array<string,array|string|integer> $context current compile context
+ *
+ * @return string Return compiled code segment for the token
+ */
+ public static function compileToken(&$token, &$context) {
+ list($raw, $vars) = static::parseTokenArgs($token, $context);
+ $named = count(array_diff_key($vars, array_keys(array_keys($vars)))) > 0;
+
+ // Handle spacing (standalone tags, partial indent)
+ static::handleMustacheSpacing($token, $vars, $context);
+
+ // Handle space control.
+ if ($token[self::POS_LSPACECTL]) {
+ $token[self::POS_LSPACE] = '';
+ }
+
+ if ($token[self::POS_RSPACECTL]) {
+ $token[self::POS_RSPACE] = '';
+ }
+
+ if ($ret = static::compileSection($token, $context, $vars, $named)) {
+ return $ret;
+ }
+
+ if ($ret = static::compileCustomHelper($context, $vars, $raw)) {
+ return $ret;
+ }
+
+ if ($ret = static::compileElse($context, $vars)) {
+ return $ret;
+ }
+
+ static::noNamedArguments($token, $context, $named, ', maybe you missing the custom helper?');
+
+ return static::compileVariable($context, $vars, $raw);
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars section token.
+ *
+ * @param array<string> $token detected handlebars {{ }} token
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array|string|integer> $vars parsed arguments list
+ * @param boolean $named is named arguments or not
+ *
+ * @return string|null Return compiled code segment for the token when the token is section
+ */
+ protected static function compileSection(&$token, &$context, &$vars, $named) {
+ switch ($token[self::POS_OP]) {
+ case '>':
+ // mustache spec: ignore missing partial
+ if (!isset($context['usedPartial'][$vars[0][0]])) {
+ return $context['ops']['seperator'];
+ }
+ $p = array_shift($vars);
+ if (!isset($vars[0])) {
+ $vars[0] = array();
+ }
+ $v = static::getVariableNames($vars, $context, true);
+ $tag = ">$p[0] " .implode(' ', $v[1]);
+ if ($context['flags']['runpart']) {
+ $sp = $context['tokens']['partialind'] ? ", '{$context['tokens']['partialind']}'" : '';
+ return $context['ops']['seperator'] . static::getFuncName($context, 'p', $tag) . "\$cx, '$p[0]', $v[0]$sp){$context['ops']['seperator']}";
+ } else {
+ if ($named || $v[0] !== 'array(array($in),array())') {
+ $context['error'][] = "Do not support {{{$tag}}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag";
+ }
+ return "{$context['ops']['seperator']}'" . static::compileTemplate($context, preg_replace('/^/m', $context['tokens']['partialind'], $context['usedPartial'][$p[0]]), $p[0]) . "'{$context['ops']['seperator']}";
+ }
+ case '^':
+ // {{^}} means {{else}}
+ if (!$vars[0][0]) {
+ $vars[0][0] = 'else';
+ $token[self::POS_OP] = '';
+ return;
+ }
+
+ // Try to compile as custom helper {{^myHelper}}
+ $r = static::compileBlockCustomHelper($context, $vars, true);
+ if ($r) {
+ return $r;
+ }
+
+ $v = static::getVariableName($vars[0], $context);
+ $context['stack'][] = $v[1];
+ $context['stack'][] = '^';
+ static::noNamedArguments($token, $context, $named);
+ // Compile to inverted section {{^myVar}}
+ return "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'isec', '^' . $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}";
+ case '/':
+ return static::compileBlockEnd($token, $context, $vars);
+ case '!':
+ case ' ':
+ return $context['ops']['seperator'];
+ case '#':
+ // Try to compile as custom helper {{#myHelper}}
+ $r = static::compileBlockCustomHelper($context, $vars);
+ if ($r) {
+ return $r;
+ }
+ static::noNamedArguments($token, $context, $named, ', maybe you missing the block custom helper?');
+ // Compile to section {{#myVar}}
+ return static::compileBlockBegin($context, $vars);
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars block custom helper begin token.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array|string|integer> $vars parsed arguments list
+ * @param boolean $inverted the logic will be inverted
+ *
+ * @return string|null Return compiled code segment for the token
+ */
+ protected static function compileBlockCustomHelper(&$context, $vars, $inverted = false) {
+ $notHBCH = !isset($context['hbhelpers'][$vars[0][0]]);
+
+ if (!isset($context['blockhelpers'][$vars[0][0]]) && $notHBCH) {
+ return;
+ }
+
+ $v = static::getVariableName($vars[0], $context);
+ $context['stack'][] = $v[1];
+ $context['stack'][] = '#';
+ $ch = array_shift($vars);
+ $inverted = $inverted ? 'true' : 'false';
+
+ static::addUsageCount($context, $notHBCH ? 'blockhelpers' : 'hbhelpers', $ch[0]);
+ $v = static::getVariableNames($vars, $context, true);
+ return $context['ops']['seperator'] . static::getFuncName($context, $notHBCH ? 'bch' : 'hbch', ($inverted ? '^' : '#') . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, \$in, $inverted, function(\$cx, \$in) {{$context['ops']['f_start']}";
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars block end token.
+ *
+ * @param array<string> $token detected handlebars {{ }} token
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array|string|integer> $vars parsed arguments list
+ *
+ * @return string Return compiled code segment for the token
+ */
+ protected static function compileBlockEnd(&$token, &$context, $vars) {
+ $each = false;
+ $pop = array_pop($context['stack']);
+ switch ($token[self::POS_INNERTAG]) {
+ case 'if':
+ case 'unless':
+ if ($pop == ':') {
+ array_pop($context['stack']);
+ return $context['usedFeature']['parent'] ? "{$context['ops']['f_end']}}){$context['ops']['seperator']}" : "{$context['ops']['cnd_end']}";
+ }
+ return $context['usedFeature']['parent'] ? "{$context['ops']['f_end']}}){$context['ops']['seperator']}" : "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
+ case 'with':
+ if ($context['flags']['with']) {
+ if ($pop !== 'with') {
+ $context['error'][] = 'Unexpect token: {{/with}} !';
+ return;
+ }
+ return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
+ }
+ break;
+ case 'each':
+ $each = true;
+ }
+
+ switch($pop) {
+ case '#':
+ case '^':
+ $pop2 = array_pop($context['stack']);
+ $v = static::getVariableName($vars[0], $context);
+ if (!$each && ($pop2 !== $v[1])) {
+ $context['error'][] = 'Unexpect token ' . static::tokenString($token) . " ! Previous token {{{$pop}$pop2}} is not closed";
+ return;
+ }
+ if ($pop == '^') {
+ return "{$context['ops']['cnd_else']}''{$context['ops']['cnd_end']}";
+ }
+ return "{$context['ops']['f_end']}}){$context['ops']['seperator']}";
+ default:
+ $context['error'][] = 'Unexpect token: ' . static::tokenString($token) . ' !';
+ return;
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars block begin token.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array|string|integer> $vars parsed arguments list
+ *
+ * @return string Return compiled code segment for the token
+ */
+ protected static function compileBlockBegin(&$context, $vars) {
+ $each = 'false';
+ $v = isset($vars[1]) ? static::getVariableName($vars[1], $context, true) : array(null, array());
+ switch ($vars[0][0]) {
+ case 'if':
+ $context['stack'][] = 'if';
+ return $context['usedFeature']['parent']
+ ? $context['ops']['seperator'] . static::getFuncName($context, 'ifv', 'if ' . $v[1]) . "\$cx, {$v[0]}, \$in, function(\$cx, \$in) {{$context['ops']['f_start']}"
+ : "{$context['ops']['cnd_start']}(" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}";
+ case 'unless':
+ $context['stack'][] = 'unless';
+ return $context['usedFeature']['parent']
+ ? $context['ops']['seperator'] . static::getFuncName($context, 'unl', 'unless ' . $v[1]) . "\$cx, {$v[0]}, \$in, function(\$cx, \$in) {{$context['ops']['f_start']}"
+ : "{$context['ops']['cnd_start']}(!" . static::getFuncName($context, 'ifvar', $v[1]) . "\$cx, {$v[0]})){$context['ops']['cnd_then']}";
+ case 'each':
+ $each = 'true';
+ array_shift($vars);
+ if (!isset($vars[0])) {
+ $vars[0] = array(null);
+ }
+ break;
+ case 'with':
+ if ($context['flags']['with']) {
+ $context['stack'][] = 'with';
+ return $context['ops']['seperator'] . static::getFuncName($context, 'wi', 'with ' . $v[1]) . "\$cx, {$v[0]}, \$in, function(\$cx, \$in) {{$context['ops']['f_start']}";
+ }
+ }
+
+ $v = static::getVariableName($vars[0], $context);
+ $context['stack'][] = $v[1];
+ $context['stack'][] = '#';
+ return $context['ops']['seperator'] . static::getFuncName($context, 'sec', (($each == 'true') ? 'each ' : '') . $v[1]) . "\$cx, {$v[0]}, \$in, $each, function(\$cx, \$in) {{$context['ops']['f_start']}";
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars custom helper token.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array|string|integer> $vars parsed arguments list
+ * @param boolean $raw is this {{{ token or not
+ * @param boolean $err should cause error when missing helper or not
+ *
+ * @return string|null Return compiled code segment for the token when the token is custom helper
+ */
+ protected static function compileCustomHelper(&$context, &$vars, $raw, $err = false) {
+ $notHH = !isset($context['hbhelpers'][$vars[0][0]]);
+ if (!isset($context['helpers'][$vars[0][0]]) && $notHH) {
+ if ($err) {
+ $context['error'][] = "Custom helper '{$vars[0][0]}' not found!";
+ }
+ return;
+ }
+
+ $fn = $raw ? 'raw' : $context['ops']['enc'];
+ $ch = array_shift($vars);
+ $v = static::getVariableNames($vars, $context, true);
+ static::addUsageCount($context, $notHH ? 'helpers' : 'hbhelpers', $ch[0]);
+ return $context['ops']['seperator'] . static::getFuncName($context, $notHH ? 'ch' : 'hbch', "$ch[0] " . implode(' ', $v[1])) . "\$cx, '$ch[0]', {$v[0]}, '$fn'" . ($notHH ? '' : ', \'$in\'') . "){$context['ops']['seperator']}";
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars else token.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array|string|integer> $vars parsed arguments list
+ *
+ * @return string|null Return compiled code segment for the token when the token is else
+ */
+ protected static function compileElse(&$context, &$vars) {
+ if ($vars[0][0] === 'else') {
+ $c = count($context['stack']) - 1;
+ if ($c >= 0) {
+ switch ($context['stack'][count($context['stack']) - 1]) {
+ case 'if':
+ case 'unless':
+ $context['stack'][] = ':';
+ return $context['usedFeature']['parent'] ? "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['f_start']}" : "{$context['ops']['cnd_else']}";
+ case 'with':
+ case 'each':
+ case '#':
+ return "{$context['ops']['f_end']}}, function(\$cx, \$in) {{$context['ops']['f_start']}";
+ }
+ }
+ $context['error'][] = '{{else}} only valid in if, unless, each, and #section context';
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Return compiled PHP code partial for a handlebars variable token.
+ *
+ * @param array<string,array|string|integer> $context current compile context
+ * @param array<array|string|integer> $vars parsed arguments list
+ * @param boolean $raw is this {{{ token or not
+ *
+ * @return string Return compiled code segment for the token
+ */
+ protected static function compileVariable(&$context, &$vars, $raw) {
+ $v = static::getVariableName($vars[0], $context);
+ if ($context['flags']['jsobj'] || $context['flags']['jstrue'] || $context['flags']['debug']) {
+ return $context['ops']['seperator'] . static::getFuncName($context, $raw ? 'raw' : $context['ops']['enc'], $v[1]) . "\$cx, {$v[0]}){$context['ops']['seperator']}";
+ } else {
+ return $raw ? "{$context['ops']['seperator']}$v[0]{$context['ops']['seperator']}" : "{$context['ops']['seperator']}htmlentities((string){$v[0]}, ENT_QUOTES, 'UTF-8'){$context['ops']['seperator']}";
+ }
+ }
+
+ /**
+ * Internal method used by compile(). Add usage count to context
+ *
+ * @param array<string,array|string|integer> $context current context
+ * @param string $category ctegory name, can be one of: 'var', 'helpers', 'blockhelpers'
+ * @param string $name used name
+ * @param integer $count increment
+ *
+ * @expect 1 when input array('usedCount' => array('test' => array())), 'test', 'testname'
+ * @expect 3 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname'
+ * @expect 5 when input array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname', 3
+ */
+ protected static function addUsageCount(&$context, $category, $name, $count = 1) {
+ if (!isset($context['usedCount'][$category][$name])) {
+ $context['usedCount'][$category][$name] = 0;
+ }
+ return ($context['usedCount'][$category][$name] += $count);
+ }
+}
+
+/**
+ * LightnCandy static class for compiled template runtime methods.
+ */
+class LCRun3 {
+ const DEBUG_ERROR_LOG = 1;
+ const DEBUG_ERROR_EXCEPTION = 2;
+ const DEBUG_TAGS = 4;
+ const DEBUG_TAGS_ANSI = 12;
+ const DEBUG_TAGS_HTML = 20;
+
+ /**
+ * LightnCandy runtime method for output debug info.
+ *
+ * @param string $v expression
+ * @param string $f runtime function name
+ * @param array<string,array|string|integer> $cx render time context
+ *
+ * @expect '{{123}}' when input '123', 'miss', array('flags' => array('debug' => LCRun3::DEBUG_TAGS)), ''
+ * @expect '<!--MISSED((-->{{#123}}<!--))--><!--SKIPPED--><!--MISSED((-->{{/123}}<!--))-->' when input '123', 'wi', array('flags' => array('debug' => LCRun3::DEBUG_TAGS_HTML)), false, false, function () {return 'A';}
+ */
+ public static function debug($v, $f, $cx) {
+ $params = array_slice(func_get_args(), 2);
+ $r = call_user_func_array((isset($cx['funcs'][$f]) ? $cx['funcs'][$f] : "LCRun3::$f"), $params);
+
+ if ($cx['flags']['debug'] & self::DEBUG_TAGS) {
+ $ansi = $cx['flags']['debug'] & (self::DEBUG_TAGS_ANSI - self::DEBUG_TAGS);
+ $html = $cx['flags']['debug'] & (self::DEBUG_TAGS_HTML - self::DEBUG_TAGS);
+ $cs = ($html ? (($r !== '') ? '<!!--OK((-->' : '<!--MISSED((-->') : '')
+ . ($ansi ? (($r !== '') ? "\033[0;32m" : "\033[0;31m") : '');
+ $ce = ($html ? '<!--))-->' : '')
+ . ($ansi ? "\033[0m" : '');
+ switch ($f) {
+ case 'sec':
+ case 'ifv':
+ case 'unl':
+ case 'wi':
+ if ($r == '') {
+ if ($ansi) {
+ $r = "\033[0;33mSKIPPED\033[0m";
+ }
+ if ($html) {
+ $r = '<!--SKIPPED-->';
+ }
+ }
+ return "$cs{{#{$v}}}$ce{$r}$cs{{/{$v}}}$ce";
+ default:
+ return "$cs{{{$v}}}$ce";
+ }
+ } else {
+ return $r;
+ }
+ }
+
+ /**
+ * LightnCandy runtime method for missing data error.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param string $v expression
+ */
+ public static function miss($cx, $v) {
+ $e = "LCRun3: $v is not exist";
+ if ($cx['flags']['debug'] & self::DEBUG_ERROR_LOG) {
+ error_log($e);
+ return;
+ }
+ if ($cx['flags']['debug'] & self::DEBUG_ERROR_EXCEPTION) {
+ throw new Exception($e);
+ }
+ }
+
+ /**
+ * LightnCandy runtime method for variable lookup. It is slower and only be used for instance property or method detection.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer> $base current variable context
+ * @param array<string|integer> $path array of names for path
+ *
+ * @return null|string Return the value or null when not found
+ *
+ * @expect null when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), 0, array('a', 'b')
+ * @expect 3 when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0), 'mustlok' => 0), array('a' => array('b' => 3)), array('a', 'b')
+ * @expect null when input array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), (Object) array('a' => array('b' => 3)), array('a', 'b')
+ * @expect 3 when input array('scopes' => array(), 'flags' => array('prop' => 1, 'method' => 0, 'mustlok' => 0)), (Object) array('a' => array('b' => 3)), array('a', 'b')
+ */
+ public static function v($cx, $base, $path) {
+ $count = count($cx['scopes']);
+ while ($base) {
+ $v = $base;
+ foreach ($path as $name) {
+ if (is_array($v) && isset($v[$name])) {
+ $v = $v[$name];
+ continue;
+ }
+ if (is_object($v)) {
+ if ($cx['flags']['prop'] && isset($v->$name)) {
+ $v = $v->$name;
+ continue;
+ }
+ if ($cx['flags']['method'] && is_callable(array($v, $name))) {
+ $v = $v->$name();
+ continue;
+ }
+ }
+ if ($cx['flags']['mustlok']) {
+ unset($v);
+ break;
+ }
+ return null;
+ }
+ if (isset($v)) {
+ return $v;
+ }
+ $count--;
+ if ($count >= 0) {
+ $base = $cx['scopes'][$count];
+ } else {
+ return null;
+ }
+ }
+ }
+
+ /**
+ * LightnCandy runtime method for {{#if var}}.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $v value to be tested
+ *
+ * @return boolean Return true when the value is not null nor false.
+ *
+ * @expect false when input array(), null
+ * @expect false when input array(), 0
+ * @expect false when input array(), false
+ * @expect true when input array(), true
+ * @expect true when input array(), 1
+ * @expect false when input array(), ''
+ * @expect false when input array(), array()
+ * @expect true when input array(), array('')
+ * @expect true when input array(), array(0)
+ */
+ public static function ifvar($cx, $v) {
+ return !is_null($v) && ($v !== false) && ($v !== 0) && ($v !== '') && (is_array($v) ? (count($v) > 0) : true);
+ }
+
+ /**
+ * LightnCandy runtime method for {{#if var}} when {{../var}} used.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $v value to be tested
+ * @param array<array|string|integer> $in input data with current scope
+ * @param Closure|null $truecb callback function when test result is true
+ * @param Closure|null $falsecb callback function when test result is false
+ *
+ * @return string The rendered string of the section
+ *
+ * @expect '' when input array('scopes' => array()), null, array(), null
+ * @expect '' when input array('scopes' => array()), null, array(), function () {return 'Y';}
+ * @expect 'Y' when input array('scopes' => array()), 1, array(), function () {return 'Y';}
+ * @expect 'N' when input array('scopes' => array()), null, array(), function () {return 'Y';}, function () {return 'N';}
+ */
+ public static function ifv($cx, $v, $in, $truecb, $falsecb = null) {
+ $ret = '';
+ if (self::ifvar($cx, $v)) {
+ if ($truecb) {
+ $cx['scopes'][] = $in;
+ $ret = $truecb($cx, $in);
+ array_pop($cx['scopes']);
+ }
+ } else {
+ if ($falsecb) {
+ $cx['scopes'][] = $in;
+ $ret = $falsecb($cx, $in);
+ array_pop($cx['scopes']);
+ }
+ }
+ return $ret;
+ }
+
+ /**
+ * LightnCandy runtime method for {{#unless var}} when {{../var}} used.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $var value be tested
+ * @param array<array|string|integer>|string|integer|null $in input data with current scope
+ * @param Closure $truecb callback function when test result is true
+ * @param Closure|null $falsecb callback function when test result is false
+ *
+ * @return string Return rendered string when the value is not null nor false.
+ *
+ * @expect '' when input array('scopes' => array()), null, array(), null
+ * @expect 'Y' when input array('scopes' => array()), null, array(), function () {return 'Y';}
+ * @expect '' when input array('scopes' => array()), 1, array(), function () {return 'Y';}
+ * @expect 'Y' when input array('scopes' => array()), null, array(), function () {return 'Y';}, function () {return 'N';}
+ * @expect 'N' when input array('scopes' => array()), true, array(), function () {return 'Y';}, function () {return 'N';}
+ */
+ public static function unl($cx, $var, $in, $truecb, $falsecb = null) {
+ return self::ifv($cx, $var, $in, $falsecb, $truecb);
+ }
+
+ /**
+ * LightnCandy runtime method for {{^var}} inverted section.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $v value to be tested
+ *
+ * @return boolean Return true when the value is not null nor false.
+ *
+ * @expect true when input array(), null
+ * @expect false when input array(), 0
+ * @expect true when input array(), false
+ * @expect false when input array(), 'false'
+ * @expect true when input array(), array()
+ * @expect false when input array(), array('1')
+ */
+ public static function isec($cx, $v) {
+ return is_null($v) || ($v === false) || (is_array($v) && (count($v) === 0));
+ }
+
+ /**
+ * LightnCandy runtime method for {{{var}}} .
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $v value to be output
+ *
+ * @return string The raw value of the specified variable
+ *
+ * @expect true when input array('flags' => array('jstrue' => 0)), true
+ * @expect 'true' when input array('flags' => array('jstrue' => 1)), true
+ * @expect '' when input array('flags' => array('jstrue' => 0)), false
+ * @expect 'false' when input array('flags' => array('jstrue' => 1)), false
+ * @expect 'false' when input array('flags' => array('jstrue' => 1)), false, true
+ * @expect 'Array' when input array('flags' => array('jstrue' => 1, 'jsobj' => 0)), array('a', 'b')
+ * @expect 'a,b' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', 'b')
+ * @expect '[object Object]' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', 'c' => 'b')
+ * @expect '[object Object]' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('c' => 'b')
+ * @expect 'a,true' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', true)
+ * @expect 'a,1' when input array('flags' => array('jstrue' => 0, 'jsobj' => 1)), array('a',true)
+ * @expect 'a,' when input array('flags' => array('jstrue' => 0, 'jsobj' => 1)), array('a',false)
+ * @expect 'a,false' when input array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a',false)
+ */
+ public static function raw($cx, $v) {
+ if ($v === true) {
+ if ($cx['flags']['jstrue']) {
+ return 'true';
+ }
+ }
+
+ if (($v === false)) {
+ if ($cx['flags']['jstrue']) {
+ return 'false';
+ }
+ }
+
+ if (is_array($v)) {
+ if ($cx['flags']['jsobj']) {
+ if (count(array_diff_key($v, array_keys(array_keys($v)))) > 0) {
+ return '[object Object]';
+ } else {
+ $ret = array();
+ foreach ($v as $k => $vv) {
+ $ret[] = self::raw($cx, $vv);
+ }
+ return join(',', $ret);
+ }
+ }
+ }
+
+ return "$v";
+ }
+
+ /**
+ * LightnCandy runtime method for {{var}} .
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $var value to be htmlencoded
+ *
+ * @return string The htmlencoded value of the specified variable
+ *
+ * @expect 'a' when input array(), 'a'
+ * @expect 'a&amp;b' when input array(), 'a&b'
+ * @expect 'a&#039;b' when input array(), 'a\'b'
+ */
+ public static function enc($cx, $var) {
+ return htmlentities(self::raw($cx, $var), ENT_QUOTES, 'UTF-8');
+ }
+
+ /**
+ * LightnCandy runtime method for {{var}} , and deal with single quote to same as handlebars.js .
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $var value to be htmlencoded
+ *
+ * @return string The htmlencoded value of the specified variable
+ *
+ * @expect 'a' when input array(), 'a'
+ * @expect 'a&amp;b' when input array(), 'a&b'
+ * @expect 'a&#x27;b' when input array(), 'a\'b'
+ * @expect '&#x60;a&#x27;b' when input array(), '`a\'b'
+ */
+ public static function encq($cx, $var) {
+ return preg_replace('/`/', '&#x60;', preg_replace('/&#039;/', '&#x27;', htmlentities(self::raw($cx, $var), ENT_QUOTES, 'UTF-8')));
+ }
+
+ /**
+ * LightnCandy runtime method for {{#var}} section.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $v value for the section
+ * @param array<array|string|integer>|string|integer|null $in input data with current scope
+ * @param boolean $each true when rendering #each
+ * @param Closure $cb callback function to render child context
+ * @param Closure|null $else callback function to render child context when {{else}}
+ *
+ * @return string The rendered string of the section
+ *
+ * @expect '' when input array('flags' => array('spvar' => 0)), false, false, false, function () {return 'A';}
+ * @expect '' when input array('flags' => array('spvar' => 0)), null, null, false, function () {return 'A';}
+ * @expect 'A' when input array('flags' => array('spvar' => 0)), true, true, false, function () {return 'A';}
+ * @expect 'A' when input array('flags' => array('spvar' => 0)), 0, 0, false, function () {return 'A';}
+ * @expect '-a=' when input array('flags' => array('spvar' => 0)), array('a'), array('a'), false, function ($c, $i) {return "-$i=";}
+ * @expect '-a=-b=' when input array('flags' => array('spvar' => 0)), array('a','b'), array('a','b'), false, function ($c, $i) {return "-$i=";}
+ * @expect '' when input array('flags' => array('spvar' => 0)), 'abc', 'abc', true, function ($c, $i) {return "-$i=";}
+ * @expect '-b=' when input array('flags' => array('spvar' => 0, 'mustsec' => 0)), array('a' => 'b'), array('a' => 'b'), true, function ($c, $i) {return "-$i=";}
+ * @expect '1' when input array('flags' => array('spvar' => 0)), 'b', 'b', false, function ($c, $i) {return count($i);}
+ * @expect '1' when input array('flags' => array('spvar' => 0)), 1, 1, false, function ($c, $i) {return print_r($i, true);}
+ * @expect '0' when input array('flags' => array('spvar' => 0)), 0, 0, false, function ($c, $i) {return print_r($i, true);}
+ * @expect '{"b":"c"}' when input array('flags' => array('spvar' => 0, 'mustsec' => 0)), array('b' => 'c'), array('b' => 'c'), false, function ($c, $i) {return json_encode($i);}
+ * @expect 'inv' when input array('flags' => array('spvar' => 0)), array(), 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'inv' when input array('flags' => array('spvar' => 0)), array(), 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'inv' when input array('flags' => array('spvar' => 0)), false, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'inv' when input array('flags' => array('spvar' => 0)), false, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'inv' when input array('flags' => array('spvar' => 0)), '', 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'cb' when input array('flags' => array('spvar' => 0)), '', 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'inv' when input array('flags' => array('spvar' => 0)), 0, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'cb' when input array('flags' => array('spvar' => 0)), 0, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'inv' when input array('flags' => array('spvar' => 0)), new stdClass, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect 'cb' when input array('flags' => array('spvar' => 0)), new stdClass, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ * @expect '268' when input array('flags' => array('spvar' => 1)), array(1,3,4), 0, false, function ($c, $i) {return $i * 2;}
+ * @expect '038' when input array('flags' => array('spvar' => 1), 'sp_vars'=>array()), array(1,3,'a'=>4), 0, true, function ($c, $i) {return $i * $c['sp_vars']['index'];}
+ */
+ public static function sec($cx, $v, $in, $each, $cb, $else = null) {
+ $isAry = is_array($v);
+ $isTrav = $v instanceof Traversable;
+ $loop = $each;
+ $keys = null;
+ $last = null;
+ $isObj = false;
+
+ if ($isAry && $else !== null && count($v) === 0) {
+ return $else($cx, $in);
+ }
+
+ // #var, detect input type is object or not
+ if (!$loop && $isAry) {
+ $keys = array_keys($v);
+ $loop = (count(array_diff_key($v, array_keys($keys))) == 0);
+ $isObj = !$loop;
+ }
+
+ if (($loop && $isAry) || $isTrav) {
+ if ($each && !$isTrav) {
+ // Detect input type is object or not when never done once
+ if ($keys == null) {
+ $keys = array_keys($v);
+ $isObj = (count(array_diff_key($v, array_keys($keys))) > 0);
+ }
+ }
+ $ret = array();
+ $cx['scopes'][] = $in;
+ $i = 0;
+ if ($cx['flags']['spvar']) {
+ $old_spvar = $cx['sp_vars'];
+ $cx['sp_vars'] = array(
+ '_parent' => $old_spvar,
+ 'root' => $old_spvar['root'],
+ );
+ if (!$isTrav) {
+ $last = count($keys) - 1;
+ }
+ }
+ foreach ($v as $index => $raw) {
+ if ($cx['flags']['spvar']) {
+ $cx['sp_vars']['first'] = ($i === 0);
+ $cx['sp_vars']['last'] = ($i == $last);
+ $cx['sp_vars']['key'] = $index;
+ $cx['sp_vars']['index'] = $i;
+ $i++;
+ }
+ $ret[] = $cb($cx, $raw);
+ }
+ if ($cx['flags']['spvar']) {
+ if ($isObj) {
+ unset($cx['sp_vars']['key']);
+ } else {
+ unset($cx['sp_vars']['last']);
+ }
+ unset($cx['sp_vars']['index']);
+ unset($cx['sp_vars']['first']);
+ $cx['sp_vars'] = $old_spvar;
+ }
+ array_pop($cx['scopes']);
+ return join('', $ret);
+ }
+ if ($each) {
+ if ($else !== null) {
+ $cx['scopes'][] = $in;
+ $ret = $else($cx, $v);
+ array_pop($cx['scopes']);
+ return $ret;
+ }
+ return '';
+ }
+ if ($isAry) {
+ if ($cx['flags']['mustsec']) {
+ $cx['scopes'][] = $v;
+ }
+ $ret = $cb($cx, $v);
+ if ($cx['flags']['mustsec']) {
+ array_pop($cx['scopes']);
+ }
+ return $ret;
+ }
+
+ if ($v === true) {
+ return $cb($cx, $in);
+ }
+
+ if (!is_null($v) && ($v !== false)) {
+ return $cb($cx, $v);
+ }
+
+ if ($else !== null) {
+ return $else($cx, $in);
+ }
+
+ return '';
+ }
+
+ /**
+ * LightnCandy runtime method for {{#with var}} .
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param array<array|string|integer>|string|integer|null $v value to be the new context
+ * @param array<array|string|integer>|string|integer|null $in input data with current scope
+ * @param Closure $cb callback function to render child context
+ * @param Closure|null $else callback function to render child context when {{else}}
+ *
+ * @return string The rendered string of the token
+ *
+ * @expect '' when input array(), false, false, function () {return 'A';}
+ * @expect '' when input array(), null, null, function () {return 'A';}
+ * @expect '-Array=' when input array(), array('a'=>'b'), array('a' => 'b'), function ($c, $i) {return "-$i=";}
+ * @expect '-b=' when input array(), 'b', array('a' => 'b'), function ($c, $i) {return "-$i=";}
+ */
+ public static function wi($cx, $v, $in, $cb, $else = null) {
+ if (($v === false) || ($v === null)) {
+ return $else ? $else($cx, $in) : '';
+ }
+ $cx['scopes'][] = $in;
+ $ret = $cb($cx, $v);
+ array_pop($cx['scopes']);
+ return $ret;
+ }
+
+ /**
+ * LightnCandy runtime method for {{> partial}} .
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param string $p partial name
+ * @param array<array|string|integer>|string|integer|null $v value to be the new context
+ *
+ * @return string The rendered string of the partial
+ *
+ */
+ public static function p($cx, $p, $v, $sp = '') {
+ return call_user_func($cx['partials'][$p], $cx, is_array($v[0][0]) ? array_merge($v[0][0], $v[1]) : $v[0][0], $sp);
+ }
+
+ /**
+ * LightnCandy runtime method for custom helpers.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param string $ch the name of custom helper to be executed
+ * @param array<array> $vars variables for the helper
+ * @param string $op the name of variable resolver. should be one of: 'raw', 'enc', or 'encq'.
+ *
+ * @return string The rendered string of the token
+ *
+ * @expect '=-=' when input array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('-'),array()), 'raw'
+ * @expect '=&amp;=' when input array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('&'),array()), 'enc'
+ * @expect '=&#x27;=' when input array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('\''),array()), 'encq'
+ * @expect '=b=' when input array('helpers' => array('a' => function ($i,$j) {return "={$j['a']}=";})), 'a', array(array(),array('a' => 'b')), 'raw'
+ */
+ public static function ch($cx, $ch, $vars, $op) {
+ return self::chret(call_user_func_array($cx['helpers'][$ch], $vars), $op);
+ }
+
+ /**
+ * LightnCandy runtime method to handle response of custom helpers.
+ *
+ * @param string|array<string,array|string|integer> $ret return value from custom helper
+ * @param string $op the name of variable resolver. should be one of: 'raw', 'enc', or 'encq'.
+ *
+ * @return string The rendered string of the token
+ *
+ * @expect '=&=' when input '=&=', 'raw'
+ * @expect '=&amp;&#039;=' when input '=&\'=', 'enc'
+ * @expect '=&amp;&#x27;=' when input '=&\'=', 'encq'
+ * @expect '=&amp;&#039;=' when input array('=&\'='), 'enc'
+ * @expect '=&amp;&#x27;=' when input array('=&\'='), 'encq'
+ * @expect '=&amp;=' when input array('=&=', false), 'enc'
+ * @expect '=&=' when input array('=&=', false), 'raw'
+ * @expect '=&=' when input array('=&=', 'raw'), 'enc'
+ * @expect '=&amp;&#x27;=' when input array('=&\'=', 'encq'), 'raw'
+ */
+ public static function chret($ret, $op) {
+ if (is_array($ret)) {
+ if (isset($ret[1]) && $ret[1]) {
+ $op = $ret[1];
+ }
+ $ret = $ret[0];
+ }
+
+ switch ($op) {
+ case 'enc':
+ return htmlentities($ret, ENT_QUOTES, 'UTF-8');
+ case 'encq':
+ return preg_replace('/&#039;/', '&#x27;', htmlentities($ret, ENT_QUOTES, 'UTF-8'));
+ }
+ return $ret;
+ }
+
+ /**
+ * LightnCandy runtime method for Handlebars.js style custom helpers.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param string $ch the name of custom helper to be executed
+ * @param array<array|string|integer>|string|integer|null $vars variables for the helper
+ * @param string $op the name of variable resolver. should be one of: 'raw', 'enc', or 'encq'.
+ * @param boolean $inverted the logic will be inverted
+ * @param Closure $cb callback function to render child context
+ * @param Closure|null $else callback function to render child context when {{else}}
+ *
+ * @return string The rendered string of the token
+ */
+ public static function hbch($cx, $ch, $vars, $op, $inverted, $cb = false, $else = false) {
+ $isBlock = (is_object($cb) && ($cb instanceof Closure));
+ $args = $vars[0];
+ $options = array(
+ 'name' => $ch,
+ 'hash' => $vars[1]
+ );
+
+ // $invert the logic
+ if ($inverted) {
+ $tmp = $else;
+ $else = $cb;
+ $cb = $tmp;
+ }
+
+ if ($isBlock) {
+ $options['fn'] = function ($context = '_NO_INPUT_HERE_') use ($cx, $op, $cb) {
+ if ($cx['flags']['echo']) {
+ ob_start();
+ }
+ if ($context === '_NO_INPUT_HERE_') {
+ $ret = $cb($cx, $op);
+ } else {
+ $cx['scopes'][] = $op;
+ $ret = $cb($cx, $context);
+ array_pop($cx['scopes']);
+ }
+ return $cx['flags']['echo'] ? ob_get_clean() : $ret;
+ };
+ }
+
+ if ($else) {
+ $options['inverse'] = function ($context = '_NO_INPUT_HERE_') use ($cx, $op, $else) {
+ if ($cx['flags']['echo']) {
+ ob_start();
+ }
+ if ($context === '_NO_INPUT_HERE_') {
+ $ret = $else($cx, $op);
+ } else {
+ $cx['scopes'][] = $op;
+ $ret = $else($cx, $context);
+ array_pop($cx['scopes']);
+ }
+ return $cx['flags']['echo'] ? ob_get_clean() : $ret;
+ };
+ }
+
+ // prepare $options['data']
+ if ($cx['flags']['spvar']) {
+ $options['data'] = $cx['sp_vars'];
+ $options['data']['root'] = $cx['scopes'][0];
+ }
+
+ $args[] = $options;
+ $e = null;
+ $r = true;
+
+ try {
+ $r = call_user_func_array($cx['hbhelpers'][$ch], $args);
+ } catch (Exception $E) {
+ $e = "LCRun3: call custom helper '$ch' error: " . $E->getMessage();
+ }
+
+ if ($r === false) {
+ if ($e === null) {
+ $e = "LCRun3: call custom helper '$ch' error";
+ }
+ }
+
+ if($e !== null) {
+ if ($cx['flags']['debug'] & self::DEBUG_ERROR_LOG) {
+ error_log($e);
+ }
+ if ($cx['flags']['debug'] & self::DEBUG_ERROR_EXCEPTION) {
+ throw new Exception($e);
+ }
+ }
+
+ return self::chret($r, $isBlock ? 'raw' : $op);
+ }
+
+ /**
+ * LightnCandy runtime method for block custom helpers.
+ *
+ * @param array<string,array|string|integer> $cx render time context
+ * @param string $ch the name of custom helper to be executed
+ * @param array<array|string|integer>|string|integer|null $vars variables for the helper
+ * @param array<array|string|integer>|string|integer|null $in input data with current scope
+ * @param boolean $inverted the logic will be inverted
+ * @param Closure $cb callback function to render child context
+ * @param Closure|null $else callback function to render child context when {{else}}
+ *
+ * @return string The rendered string of the token
+ *
+ * @expect '4.2.3' when input array('blockhelpers' => array('a' => function ($cx) {return array($cx,2,3);})), 'a', array(0, 0), 4, false, function($cx, $i) {return implode('.', $i);}
+ * @expect '2.6.5' when input array('blockhelpers' => array('a' => function ($cx,$in) {return array($cx,$in[0],5);})), 'a', array('6', 0), 2, false, function($cx, $i) {return implode('.', $i);}
+ * @expect '' when input array('blockhelpers' => array('a' => function ($cx,$in) {})), 'a', array('6', 0), 2, false, function($cx, $i) {return implode('.', $i);}
+ */
+ public static function bch($cx, $ch, $vars, $in, $inverted, $cb, $else = false) {
+ $r = call_user_func($cx['blockhelpers'][$ch], $in, $vars[0], $vars[1]);
+
+ // $invert the logic
+ if ($inverted) {
+ $tmp = $else;
+ $else = $cb;
+ $cb = $tmp;
+ }
+
+ $ret = '';
+ if (is_null($r)) {
+ if ($else) {
+ $cx['scopes'][] = $in;
+ $ret = $else($cx, $r);
+ array_pop($cx['scopes']);
+ }
+ } else {
+ if ($cb) {
+ $cx['scopes'][] = $in;
+ $ret = $cb($cx, $r);
+ array_pop($cx['scopes']);
+ }
+ }
+
+ return $ret;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/vendor/zordius/lightncandy/tests/LCRun3Test.php b/vendor/zordius/lightncandy/tests/LCRun3Test.php
new file mode 100644
index 00000000..63d6348e
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/LCRun3Test.php
@@ -0,0 +1,376 @@
+<?php
+/**
+ * Generated by build/gen_test
+ */
+require_once('src/lightncandy.php');
+
+class LCRun3Test extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers LCRun3::debug
+ */
+ public function testOn_debug() {
+ $method = new ReflectionMethod('LCRun3', 'debug');
+ $this->assertEquals('{{123}}', $method->invoke(null,
+ '123', 'miss', array('flags' => array('debug' => LCRun3::DEBUG_TAGS)), ''
+ ));
+ $this->assertEquals('<!--MISSED((-->{{#123}}<!--))--><!--SKIPPED--><!--MISSED((-->{{/123}}<!--))-->', $method->invoke(null,
+ '123', 'wi', array('flags' => array('debug' => LCRun3::DEBUG_TAGS_HTML)), false, false, function () {return 'A';}
+ ));
+ }
+ /**
+ * @covers LCRun3::v
+ */
+ public function testOn_v() {
+ $method = new ReflectionMethod('LCRun3', 'v');
+ $this->assertEquals(null, $method->invoke(null,
+ array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), 0, array('a', 'b')
+ ));
+ $this->assertEquals(3, $method->invoke(null,
+ array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0), 'mustlok' => 0), array('a' => array('b' => 3)), array('a', 'b')
+ ));
+ $this->assertEquals(null, $method->invoke(null,
+ array('scopes' => array(), 'flags' => array('prop' => 0, 'method' => 0, 'mustlok' => 0)), (Object) array('a' => array('b' => 3)), array('a', 'b')
+ ));
+ $this->assertEquals(3, $method->invoke(null,
+ array('scopes' => array(), 'flags' => array('prop' => 1, 'method' => 0, 'mustlok' => 0)), (Object) array('a' => array('b' => 3)), array('a', 'b')
+ ));
+ }
+ /**
+ * @covers LCRun3::ifvar
+ */
+ public function testOn_ifvar() {
+ $method = new ReflectionMethod('LCRun3', 'ifvar');
+ $this->assertEquals(false, $method->invoke(null,
+ array(), null
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array(), 0
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array(), false
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(), true
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(), 1
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array(), ''
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array(), array()
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(), array('')
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(), array(0)
+ ));
+ }
+ /**
+ * @covers LCRun3::ifv
+ */
+ public function testOn_ifv() {
+ $method = new ReflectionMethod('LCRun3', 'ifv');
+ $this->assertEquals('', $method->invoke(null,
+ array('scopes' => array()), null, array(), null
+ ));
+ $this->assertEquals('', $method->invoke(null,
+ array('scopes' => array()), null, array(), function () {return 'Y';}
+ ));
+ $this->assertEquals('Y', $method->invoke(null,
+ array('scopes' => array()), 1, array(), function () {return 'Y';}
+ ));
+ $this->assertEquals('N', $method->invoke(null,
+ array('scopes' => array()), null, array(), function () {return 'Y';}, function () {return 'N';}
+ ));
+ }
+ /**
+ * @covers LCRun3::unl
+ */
+ public function testOn_unl() {
+ $method = new ReflectionMethod('LCRun3', 'unl');
+ $this->assertEquals('', $method->invoke(null,
+ array('scopes' => array()), null, array(), null
+ ));
+ $this->assertEquals('Y', $method->invoke(null,
+ array('scopes' => array()), null, array(), function () {return 'Y';}
+ ));
+ $this->assertEquals('', $method->invoke(null,
+ array('scopes' => array()), 1, array(), function () {return 'Y';}
+ ));
+ $this->assertEquals('Y', $method->invoke(null,
+ array('scopes' => array()), null, array(), function () {return 'Y';}, function () {return 'N';}
+ ));
+ $this->assertEquals('N', $method->invoke(null,
+ array('scopes' => array()), true, array(), function () {return 'Y';}, function () {return 'N';}
+ ));
+ }
+ /**
+ * @covers LCRun3::isec
+ */
+ public function testOn_isec() {
+ $method = new ReflectionMethod('LCRun3', 'isec');
+ $this->assertEquals(true, $method->invoke(null,
+ array(), null
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array(), 0
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(), false
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array(), 'false'
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(), array()
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array(), array('1')
+ ));
+ }
+ /**
+ * @covers LCRun3::raw
+ */
+ public function testOn_raw() {
+ $method = new ReflectionMethod('LCRun3', 'raw');
+ $this->assertEquals(true, $method->invoke(null,
+ array('flags' => array('jstrue' => 0)), true
+ ));
+ $this->assertEquals('true', $method->invoke(null,
+ array('flags' => array('jstrue' => 1)), true
+ ));
+ $this->assertEquals('', $method->invoke(null,
+ array('flags' => array('jstrue' => 0)), false
+ ));
+ $this->assertEquals('false', $method->invoke(null,
+ array('flags' => array('jstrue' => 1)), false
+ ));
+ $this->assertEquals('false', $method->invoke(null,
+ array('flags' => array('jstrue' => 1)), false, true
+ ));
+ $this->assertEquals('Array', $method->invoke(null,
+ array('flags' => array('jstrue' => 1, 'jsobj' => 0)), array('a', 'b')
+ ));
+ $this->assertEquals('a,b', $method->invoke(null,
+ array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', 'b')
+ ));
+ $this->assertEquals('[object Object]', $method->invoke(null,
+ array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', 'c' => 'b')
+ ));
+ $this->assertEquals('[object Object]', $method->invoke(null,
+ array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('c' => 'b')
+ ));
+ $this->assertEquals('a,true', $method->invoke(null,
+ array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a', true)
+ ));
+ $this->assertEquals('a,1', $method->invoke(null,
+ array('flags' => array('jstrue' => 0, 'jsobj' => 1)), array('a',true)
+ ));
+ $this->assertEquals('a,', $method->invoke(null,
+ array('flags' => array('jstrue' => 0, 'jsobj' => 1)), array('a',false)
+ ));
+ $this->assertEquals('a,false', $method->invoke(null,
+ array('flags' => array('jstrue' => 1, 'jsobj' => 1)), array('a',false)
+ ));
+ }
+ /**
+ * @covers LCRun3::enc
+ */
+ public function testOn_enc() {
+ $method = new ReflectionMethod('LCRun3', 'enc');
+ $this->assertEquals('a', $method->invoke(null,
+ array(), 'a'
+ ));
+ $this->assertEquals('a&amp;b', $method->invoke(null,
+ array(), 'a&b'
+ ));
+ $this->assertEquals('a&#039;b', $method->invoke(null,
+ array(), 'a\'b'
+ ));
+ }
+ /**
+ * @covers LCRun3::encq
+ */
+ public function testOn_encq() {
+ $method = new ReflectionMethod('LCRun3', 'encq');
+ $this->assertEquals('a', $method->invoke(null,
+ array(), 'a'
+ ));
+ $this->assertEquals('a&amp;b', $method->invoke(null,
+ array(), 'a&b'
+ ));
+ $this->assertEquals('a&#x27;b', $method->invoke(null,
+ array(), 'a\'b'
+ ));
+ $this->assertEquals('&#x60;a&#x27;b', $method->invoke(null,
+ array(), '`a\'b'
+ ));
+ }
+ /**
+ * @covers LCRun3::sec
+ */
+ public function testOn_sec() {
+ $method = new ReflectionMethod('LCRun3', 'sec');
+ $this->assertEquals('', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), false, false, false, function () {return 'A';}
+ ));
+ $this->assertEquals('', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), null, null, false, function () {return 'A';}
+ ));
+ $this->assertEquals('A', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), true, true, false, function () {return 'A';}
+ ));
+ $this->assertEquals('A', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), 0, 0, false, function () {return 'A';}
+ ));
+ $this->assertEquals('-a=', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), array('a'), array('a'), false, function ($c, $i) {return "-$i=";}
+ ));
+ $this->assertEquals('-a=-b=', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), array('a','b'), array('a','b'), false, function ($c, $i) {return "-$i=";}
+ ));
+ $this->assertEquals('', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), 'abc', 'abc', true, function ($c, $i) {return "-$i=";}
+ ));
+ $this->assertEquals('-b=', $method->invoke(null,
+ array('flags' => array('spvar' => 0, 'mustsec' => 0)), array('a' => 'b'), array('a' => 'b'), true, function ($c, $i) {return "-$i=";}
+ ));
+ $this->assertEquals('1', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), 'b', 'b', false, function ($c, $i) {return count($i);}
+ ));
+ $this->assertEquals('1', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), 1, 1, false, function ($c, $i) {return print_r($i, true);}
+ ));
+ $this->assertEquals('0', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), 0, 0, false, function ($c, $i) {return print_r($i, true);}
+ ));
+ $this->assertEquals('{"b":"c"}', $method->invoke(null,
+ array('flags' => array('spvar' => 0, 'mustsec' => 0)), array('b' => 'c'), array('b' => 'c'), false, function ($c, $i) {return json_encode($i);}
+ ));
+ $this->assertEquals('inv', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), array(), 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('inv', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), array(), 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('inv', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), false, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('inv', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), false, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('inv', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), '', 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('cb', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), '', 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('inv', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), 0, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('cb', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), 0, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('inv', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), new stdClass, 0, true, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('cb', $method->invoke(null,
+ array('flags' => array('spvar' => 0)), new stdClass, 0, false, function ($c, $i) {return 'cb';}, function ($c, $i) {return 'inv';}
+ ));
+ $this->assertEquals('268', $method->invoke(null,
+ array('flags' => array('spvar' => 1)), array(1,3,4), 0, false, function ($c, $i) {return $i * 2;}
+ ));
+ $this->assertEquals('038', $method->invoke(null,
+ array('flags' => array('spvar' => 1), 'sp_vars'=>array()), array(1,3,'a'=>4), 0, true, function ($c, $i) {return $i * $c['sp_vars']['index'];}
+ ));
+ }
+ /**
+ * @covers LCRun3::wi
+ */
+ public function testOn_wi() {
+ $method = new ReflectionMethod('LCRun3', 'wi');
+ $this->assertEquals('', $method->invoke(null,
+ array(), false, false, function () {return 'A';}
+ ));
+ $this->assertEquals('', $method->invoke(null,
+ array(), null, null, function () {return 'A';}
+ ));
+ $this->assertEquals('-Array=', $method->invoke(null,
+ array(), array('a'=>'b'), array('a' => 'b'), function ($c, $i) {return "-$i=";}
+ ));
+ $this->assertEquals('-b=', $method->invoke(null,
+ array(), 'b', array('a' => 'b'), function ($c, $i) {return "-$i=";}
+ ));
+ }
+ /**
+ * @covers LCRun3::ch
+ */
+ public function testOn_ch() {
+ $method = new ReflectionMethod('LCRun3', 'ch');
+ $this->assertEquals('=-=', $method->invoke(null,
+ array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('-'),array()), 'raw'
+ ));
+ $this->assertEquals('=&amp;=', $method->invoke(null,
+ array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('&'),array()), 'enc'
+ ));
+ $this->assertEquals('=&#x27;=', $method->invoke(null,
+ array('helpers' => array('a' => function ($i) {return "=$i[0]=";})), 'a', array(array('\''),array()), 'encq'
+ ));
+ $this->assertEquals('=b=', $method->invoke(null,
+ array('helpers' => array('a' => function ($i,$j) {return "={$j['a']}=";})), 'a', array(array(),array('a' => 'b')), 'raw'
+ ));
+ }
+ /**
+ * @covers LCRun3::chret
+ */
+ public function testOn_chret() {
+ $method = new ReflectionMethod('LCRun3', 'chret');
+ $this->assertEquals('=&=', $method->invoke(null,
+ '=&=', 'raw'
+ ));
+ $this->assertEquals('=&amp;&#039;=', $method->invoke(null,
+ '=&\'=', 'enc'
+ ));
+ $this->assertEquals('=&amp;&#x27;=', $method->invoke(null,
+ '=&\'=', 'encq'
+ ));
+ $this->assertEquals('=&amp;&#039;=', $method->invoke(null,
+ array('=&\'='), 'enc'
+ ));
+ $this->assertEquals('=&amp;&#x27;=', $method->invoke(null,
+ array('=&\'='), 'encq'
+ ));
+ $this->assertEquals('=&amp;=', $method->invoke(null,
+ array('=&=', false), 'enc'
+ ));
+ $this->assertEquals('=&=', $method->invoke(null,
+ array('=&=', false), 'raw'
+ ));
+ $this->assertEquals('=&=', $method->invoke(null,
+ array('=&=', 'raw'), 'enc'
+ ));
+ $this->assertEquals('=&amp;&#x27;=', $method->invoke(null,
+ array('=&\'=', 'encq'), 'raw'
+ ));
+ }
+ /**
+ * @covers LCRun3::bch
+ */
+ public function testOn_bch() {
+ $method = new ReflectionMethod('LCRun3', 'bch');
+ $this->assertEquals('4.2.3', $method->invoke(null,
+ array('blockhelpers' => array('a' => function ($cx) {return array($cx,2,3);})), 'a', array(0, 0), 4, false, function($cx, $i) {return implode('.', $i);}
+ ));
+ $this->assertEquals('2.6.5', $method->invoke(null,
+ array('blockhelpers' => array('a' => function ($cx,$in) {return array($cx,$in[0],5);})), 'a', array('6', 0), 2, false, function($cx, $i) {return implode('.', $i);}
+ ));
+ $this->assertEquals('', $method->invoke(null,
+ array('blockhelpers' => array('a' => function ($cx,$in) {})), 'a', array('6', 0), 2, false, function($cx, $i) {return implode('.', $i);}
+ ));
+ }
+}
+?> \ No newline at end of file
diff --git a/vendor/zordius/lightncandy/tests/LightnCandyTest.php b/vendor/zordius/lightncandy/tests/LightnCandyTest.php
new file mode 100644
index 00000000..6f82f2bd
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/LightnCandyTest.php
@@ -0,0 +1,460 @@
+<?php
+/**
+ * Generated by build/gen_test
+ */
+require_once('src/lightncandy.php');
+
+class LightnCandyTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @covers LightnCandy::buildHelperTable
+ */
+ public function testOn_buildHelperTable() {
+ $method = new ReflectionMethod('LightnCandy', 'buildHelperTable');
+ $method->setAccessible(true);
+ $this->assertEquals(array(), $method->invoke(null,
+ array(), array()
+ ));
+ $this->assertEquals(array('flags' => array('exhlp' => 1)), $method->invoke(null,
+ array('flags' => array('exhlp' => 1)), array('helpers' => array('abc'))
+ ));
+ $this->assertEquals(array('error' => array('Can not find custom helper function defination abc() !'), 'flags' => array('exhlp' => 0)), $method->invoke(null,
+ array('error' => array(), 'flags' => array('exhlp' => 0)), array('helpers' => array('abc'))
+ ));
+ $this->assertEquals(array('flags' => array('exhlp' => 1), 'helpers' => array('LCRun3::raw' => 'LCRun3::raw')), $method->invoke(null,
+ array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('LCRun3::raw'))
+ ));
+ $this->assertEquals(array('flags' => array('exhlp' => 1), 'helpers' => array('test' => 'LCRun3::raw')), $method->invoke(null,
+ array('flags' => array('exhlp' => 1), 'helpers' => array()), array('helpers' => array('test' => 'LCRun3::raw'))
+ ));
+ }
+ /**
+ * @covers LightnCandy::buildCXFileext
+ */
+ public function testOn_buildCXFileext() {
+ $method = new ReflectionMethod('LightnCandy', 'buildCXFileext');
+ $method->setAccessible(true);
+ $this->assertEquals(array('.tmpl'), $method->invoke(null,
+ array()
+ ));
+ $this->assertEquals(array('test'), $method->invoke(null,
+ array('fileext' => 'test')
+ ));
+ $this->assertEquals(array('test1'), $method->invoke(null,
+ array('fileext' => array('test1'))
+ ));
+ $this->assertEquals(array('test2', 'test3'), $method->invoke(null,
+ array('fileext' => array('test2', 'test3'))
+ ));
+ }
+ /**
+ * @covers LightnCandy::buildCXBasedir
+ */
+ public function testOn_buildCXBasedir() {
+ $method = new ReflectionMethod('LightnCandy', 'buildCXBasedir');
+ $method->setAccessible(true);
+ $this->assertEquals(array(), $method->invoke(null,
+ array()
+ ));
+ $this->assertEquals(array(), $method->invoke(null,
+ array('basedir' => array())
+ ));
+ $this->assertEquals(array('src'), $method->invoke(null,
+ array('basedir' => array('src'))
+ ));
+ $this->assertEquals(array('src'), $method->invoke(null,
+ array('basedir' => array('src', 'dir_not_found'))
+ ));
+ $this->assertEquals(array('src', 'tests'), $method->invoke(null,
+ array('basedir' => array('src', 'tests'))
+ ));
+ }
+ /**
+ * @covers LightnCandy::getPHPCode
+ */
+ public function testOn_getPHPCode() {
+ $method = new ReflectionMethod('LightnCandy', 'getPHPCode');
+ $method->setAccessible(true);
+ $this->assertEquals('function($a) {return;}', $method->invoke(null,
+ function ($a) {return;}
+ ));
+ $this->assertEquals('function($a) {return;}', $method->invoke(null,
+ function ($a) {return;}
+ ));
+ }
+ /**
+ * @covers LightnCandy::handleError
+ */
+ public function testOn_handleError() {
+ $method = new ReflectionMethod('LightnCandy', 'handleError');
+ $method->setAccessible(true);
+ $this->assertEquals(true, $method->invoke(null,
+ array('level' => 1, 'stack' => array('X'), 'flags' => array('errorlog' => 0, 'exception' => 0), 'error' => array())
+ ));
+ $this->assertEquals(false, $method->invoke(null,
+ array('level' => 0, 'error' => array())
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array('level' => 0, 'error' => array('some error'), 'flags' => array('errorlog' => 0, 'exception' => 0))
+ ));
+ }
+ /**
+ * @covers LightnCandy::getBoolStr
+ */
+ public function testOn_getBoolStr() {
+ $method = new ReflectionMethod('LightnCandy', 'getBoolStr');
+ $method->setAccessible(true);
+ $this->assertEquals('true', $method->invoke(null,
+ 1
+ ));
+ $this->assertEquals('true', $method->invoke(null,
+ 999
+ ));
+ $this->assertEquals('false', $method->invoke(null,
+ 0
+ ));
+ $this->assertEquals('false', $method->invoke(null,
+ -1
+ ));
+ }
+ /**
+ * @covers LightnCandy::getFuncName
+ */
+ public function testOn_getFuncName() {
+ $method = new ReflectionMethod('LightnCandy', 'getFuncName');
+ $method->setAccessible(true);
+ $this->assertEquals('LCRun3::test(', $method->invoke(null,
+ array('flags' => array('standalone' => 0, 'debug' => 0)), 'test', ''
+ ));
+ $this->assertEquals('LCRun3::test2(', $method->invoke(null,
+ array('flags' => array('standalone' => 0, 'debug' => 0)), 'test2', ''
+ ));
+ $this->assertEquals("\$cx['funcs']['test3'](", $method->invoke(null,
+ array('flags' => array('standalone' => 1, 'debug' => 0)), 'test3', ''
+ ));
+ $this->assertEquals('LCRun3::debug(\'abc\', \'test\', ', $method->invoke(null,
+ array('flags' => array('standalone' => 0, 'debug' => 1)), 'test', 'abc'
+ ));
+ }
+ /**
+ * @covers LightnCandy::getArrayStr
+ */
+ public function testOn_getArrayStr() {
+ $method = new ReflectionMethod('LightnCandy', 'getArrayStr');
+ $method->setAccessible(true);
+ $this->assertEquals('', $method->invoke(null,
+ array()
+ ));
+ $this->assertEquals('[a]', $method->invoke(null,
+ array('a')
+ ));
+ $this->assertEquals('[a][b][c]', $method->invoke(null,
+ array('a', 'b', 'c')
+ ));
+ }
+ /**
+ * @covers LightnCandy::getArrayCode
+ */
+ public function testOn_getArrayCode() {
+ $method = new ReflectionMethod('LightnCandy', 'getArrayCode');
+ $method->setAccessible(true);
+ $this->assertEquals('', $method->invoke(null,
+ array()
+ ));
+ $this->assertEquals("['a']", $method->invoke(null,
+ array('a')
+ ));
+ $this->assertEquals("['a']['b']['c']", $method->invoke(null,
+ array('a', 'b', 'c')
+ ));
+ }
+ /**
+ * @covers LightnCandy::getVariableNames
+ */
+ public function testOn_getVariableNames() {
+ $method = new ReflectionMethod('LightnCandy', 'getVariableNames');
+ $method->setAccessible(true);
+ $this->assertEquals(array('array(array($in),array())', array('this')), $method->invoke(null,
+ array(null), array('flags'=>array('spvar'=>true))
+ ));
+ $this->assertEquals(array('array(array($in,$in),array())', array('this', 'this')), $method->invoke(null,
+ array(null, null), array('flags'=>array('spvar'=>true))
+ ));
+ $this->assertEquals(array('array(array(),array(\'a\'=>$in))', array('this')), $method->invoke(null,
+ array('a' => null), array('flags'=>array('spvar'=>true))
+ ));
+ }
+ /**
+ * @covers LightnCandy::getVariableName
+ */
+ public function testOn_getVariableName() {
+ $method = new ReflectionMethod('LightnCandy', 'getVariableName');
+ $method->setAccessible(true);
+ $this->assertEquals(array('$in', 'this'), $method->invoke(null,
+ array(null), array('flags'=>array('spvar'=>true,'debug'=>0))
+ ));
+ $this->assertEquals(array('true', 'true'), $method->invoke(null,
+ array('true'), array('flags'=>array('spvar'=>true,'debug'=>0)), true
+ ));
+ $this->assertEquals(array('false', 'false'), $method->invoke(null,
+ array('false'), array('flags'=>array('spvar'=>true,'debug'=>0)), true
+ ));
+ $this->assertEquals(array(2, '2'), $method->invoke(null,
+ array('2'), array('flags'=>array('spvar'=>true,'debug'=>0)), true
+ ));
+ $this->assertEquals(array('((isset($in[\'@index\']) && is_array($in)) ? $in[\'@index\'] : null)', '[@index]'), $method->invoke(null,
+ array('@index'), array('flags'=>array('spvar'=>false,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ ));
+ $this->assertEquals(array("((isset(\$cx['sp_vars']['index']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['index'] : null)", '@[index]'), $method->invoke(null,
+ array('@index'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ ));
+ $this->assertEquals(array("((isset(\$cx['sp_vars']['key']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['key'] : null)", '@[key]'), $method->invoke(null,
+ array('@key'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ ));
+ $this->assertEquals(array("((isset(\$cx['sp_vars']['first']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['first'] : null)", '@[first]'), $method->invoke(null,
+ array('@first'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ ));
+ $this->assertEquals(array("((isset(\$cx['sp_vars']['last']) && is_array(\$cx['sp_vars'])) ? \$cx['sp_vars']['last'] : null)", '@[last]'), $method->invoke(null,
+ array('@last'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ ));
+ $this->assertEquals(array('\'a\'', '"a"'), $method->invoke(null,
+ array('"a"'), array('flags'=>array('spvar'=>true,'debug'=>0))
+ ));
+ $this->assertEquals(array('((isset($in[\'a\']) && is_array($in)) ? $in[\'a\'] : null)', '[a]'), $method->invoke(null,
+ array('a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ ));
+ $this->assertEquals(array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-1])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-1][\'a\'] : null)', '../[a]'), $method->invoke(null,
+ array(1,'a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ ));
+ $this->assertEquals(array('((isset($cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\']) && is_array($cx[\'scopes\'][count($cx[\'scopes\'])-3])) ? $cx[\'scopes\'][count($cx[\'scopes\'])-3][\'a\'] : null)', '../../../[a]'), $method->invoke(null,
+ array(3,'a'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ ));
+ $this->assertEquals(array('((isset($in[\'id\']) && is_array($in)) ? $in[\'id\'] : null)', 'this.[id]'), $method->invoke(null,
+ array(null, 'id'), array('flags'=>array('spvar'=>true,'debug'=>0,'prop'=>0,'method'=>0,'mustlok'=>0))
+ ));
+ $this->assertEquals(array('LCRun3::v($cx, $in, array(\'id\'))', 'this.[id]'), $method->invoke(null,
+ array(null, 'id'), array('flags'=>array('prop'=>true,'spvar'=>true,'debug'=>0,'method'=>0,'mustlok'=>0,'standalone'=>0))
+ ));
+ }
+ /**
+ * @covers LightnCandy::getExpression
+ */
+ public function testOn_getExpression() {
+ $method = new ReflectionMethod('LightnCandy', 'getExpression');
+ $method->setAccessible(true);
+ $this->assertEquals('[a].[b]', $method->invoke(null,
+ 0, false, array('a', 'b')
+ ));
+ $this->assertEquals('@[root]', $method->invoke(null,
+ 0, true, array('root')
+ ));
+ $this->assertEquals('this', $method->invoke(null,
+ 0, false, null
+ ));
+ $this->assertEquals('this.[id]', $method->invoke(null,
+ 0, false, array(null, 'id')
+ ));
+ $this->assertEquals('@[root].[a].[b]', $method->invoke(null,
+ 0, true, array('root', 'a', 'b')
+ ));
+ $this->assertEquals('../../[a].[b]', $method->invoke(null,
+ 2, false, array('a', 'b')
+ ));
+ $this->assertEquals('../[a\'b]', $method->invoke(null,
+ 1, false, array('a\'b')
+ ));
+ }
+ /**
+ * @covers LightnCandy::fixVariable
+ */
+ public function testOn_fixVariable() {
+ $method = new ReflectionMethod('LightnCandy', 'fixVariable');
+ $method->setAccessible(true);
+ $this->assertEquals(array('this'), $method->invoke(null,
+ 'this', array('flags' => array('advar' => 0, 'this' => 0))
+ ));
+ $this->assertEquals(array(null), $method->invoke(null,
+ 'this', array('flags' => array('advar' => 0, 'this' => 1))
+ ));
+ $this->assertEquals(array(1, null), $method->invoke(null,
+ '../', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array(1, null), $method->invoke(null,
+ '../.', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array(1, null), $method->invoke(null,
+ '../this', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array(1, 'a'), $method->invoke(null,
+ '../a', array('flags' => array('advar' => 0, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array(2, 'a', 'b'), $method->invoke(null,
+ '../../a.b', array('flags' => array('advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array(2, '[a]', 'b'), $method->invoke(null,
+ '../../[a].b', array('flags' => array('advar' => 0, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array(2, 'a', 'b'), $method->invoke(null,
+ '../../[a].b', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array('"a.b"'), $method->invoke(null,
+ '"a.b"', array('flags' => array('advar' => 1, 'this' => 0, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ $this->assertEquals(array(null, 'id'), $method->invoke(null,
+ 'this.id', array('flags' => array('advar' => 1, 'this' => 1, 'parent' => 1), 'usedFeature' => array('parent' => 0))
+ ));
+ }
+ /**
+ * @covers LightnCandy::parseTokenArgs
+ */
+ public function testOn_parseTokenArgs() {
+ $method = new ReflectionMethod('LightnCandy', 'parseTokenArgs');
+ $method->setAccessible(true);
+ $this->assertEquals(array(false, array(array(null))), $method->invoke(null,
+ array(0,0,0,0,0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(true, array(array(null))), $method->invoke(null,
+ array(0,0,0,'{{{',0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(true, array(array(null))), $method->invoke(null,
+ array(0,0,0,0,0,0,''), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 1))
+ ));
+ $this->assertEquals(array(false, array(array('a'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('b'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a b'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('"b'), array('c"'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a "b c"'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('"b c"'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a "b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('[b'), array('c]'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('[b'), array('c]'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('b c'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 0, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('b c'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a [b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), 'q' => array('b c'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), array('q=[b c'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a [q=[b c]'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), 'q' => array('[b'), array('c]'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a q=[b c]'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), 'q' => array('b'), array('c'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a [q]=b c'), array('flags' => array('advar' => 0, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('a'), 'q' => array('"b c"'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'a q="b c"'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('(foo bar)'))), $method->invoke(null,
+ array(0,0,0,0,0,0,'(foo bar)'), array('flags' => array('advar' => 1, 'this' => 1, 'namev' => 1, 'noesc' => 0))
+ ));
+ $this->assertEquals(array(false, array(array('foo'), array("'=='"), array('bar'))), $method->invoke(null,
+ array(0,0,0,0,0,0,"foo '==' bar"), array('flags' => array('advar' => 1, 'namev' => 1))
+ ));
+ }
+ /**
+ * @covers LightnCandy::tokenString
+ */
+ public function testOn_tokenString() {
+ $method = new ReflectionMethod('LightnCandy', 'tokenString');
+ $method->setAccessible(true);
+ $this->assertEquals('b', $method->invoke(null,
+ array(0, 'a', 'b', 'c'), 1
+ ));
+ $this->assertEquals('c', $method->invoke(null,
+ array(0, 'a', 'b', 'c', 'd', 'e')
+ ));
+ }
+ /**
+ * @covers LightnCandy::validateStartEnd
+ */
+ public function testOn_validateStartEnd() {
+ $method = new ReflectionMethod('LightnCandy', 'validateStartEnd');
+ $method->setAccessible(true);
+ $this->assertEquals(null, $method->invoke(null,
+ array_fill(0, 9, ''), array()
+ ));
+ $this->assertEquals(null, $method->invoke(null,
+ array_fill(0, 9, '}}'), array()
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array_fill(0, 9, '{{{'), array()
+ ));
+ }
+ /**
+ * @covers LightnCandy::validateOperations
+ */
+ public function testOn_validateOperations() {
+ $method = new ReflectionMethod('LightnCandy', 'validateOperations');
+ $method->setAccessible(true);
+ $this->assertEquals(null, $method->invoke(null,
+ array(0, 0, 0, 0, 0, ''), array(), array()
+ ));
+ $this->assertEquals(2, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '^', '...'), array('usedFeature' => array('isec' => 1), 'level' => 0), array(array('foo'))
+ ));
+ $this->assertEquals(3, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '!', '...'), array('usedFeature' => array('comment' => 2)), array()
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '/'), array('stack' => array(1), 'level' => 1), array()
+ ));
+ $this->assertEquals(4, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('sec' => 3), 'level' => 0), array(array('x'))
+ ));
+ $this->assertEquals(5, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('if' => 4), 'level' => 0), array(array('if'))
+ ));
+ $this->assertEquals(6, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('with' => 5), 'level' => 0, 'flags' => array('with' => 1)), array(array('with'))
+ ));
+ $this->assertEquals(7, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('each' => 6), 'level' => 0), array(array('each'))
+ ));
+ $this->assertEquals(8, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '#', '...'), array('usedFeature' => array('unless' => 7), 'level' => 0), array(array('unless'))
+ ));
+ $this->assertEquals(9, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '#', '...'), array('blockhelpers' => array('abc' => ''), 'usedFeature' => array('bhelper' => 8), 'level' => 0), array(array('abc'))
+ ));
+ $this->assertEquals(10, $method->invoke(null,
+ array(0, 0, 0, 0, 0, ' ', '...'), array('usedFeature' => array('delimiter' => 9), 'level' => 0), array()
+ ));
+ $this->assertEquals(11, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '#', '...'), array('hbhelpers' => array('abc' => ''), 'usedFeature' => array('hbhelper' => 10), 'level' => 0), array(array('abc'))
+ ));
+ $this->assertEquals(true, $method->invoke(null,
+ array(0, 0, 0, 0, 0, '>', '...'), array('basedir' => array('.'), 'fileext' => array('.tmpl'), 'usedFeature' => array('unless' => 7, 'partial' => 7), 'level' => 0, 'flags' => array('skippartial' => 0)), array('test')
+ ));
+ }
+ /**
+ * @covers LightnCandy::addUsageCount
+ */
+ public function testOn_addUsageCount() {
+ $method = new ReflectionMethod('LightnCandy', 'addUsageCount');
+ $method->setAccessible(true);
+ $this->assertEquals(1, $method->invoke(null,
+ array('usedCount' => array('test' => array())), 'test', 'testname'
+ ));
+ $this->assertEquals(3, $method->invoke(null,
+ array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname'
+ ));
+ $this->assertEquals(5, $method->invoke(null,
+ array('usedCount' => array('test' => array('testname' => 2))), 'test', 'testname', 3
+ ));
+ }
+}
+?> \ No newline at end of file
diff --git a/vendor/zordius/lightncandy/tests/errorTest.php b/vendor/zordius/lightncandy/tests/errorTest.php
new file mode 100644
index 00000000..30ecd97d
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/errorTest.php
@@ -0,0 +1,410 @@
+<?php
+
+require_once('src/lightncandy.php');
+require_once('tests/helpers_for_test.php');
+
+$tmpdir = sys_get_temp_dir();
+$errlog_fn = tempnam($tmpdir, 'terr_');
+
+function start_catch_error_log() {
+ global $errlog_fn;
+ date_default_timezone_set('GMT');
+ if (file_exists($errlog_fn)) {
+ unlink($errlog_fn);
+ }
+ return ini_set('error_log', $errlog_fn);
+}
+
+function stop_catch_error_log() {
+ global $errlog_fn;
+ ini_restore('error_log');
+ if (!file_exists($errlog_fn)) {
+ return null;
+ }
+ return array_map(function ($l) {
+ $l = rtrim($l);
+ preg_match('/GMT\] (.+)/', $l, $m);
+ return isset($m[1]) ? $m[1] : $l;
+ }, file($errlog_fn));
+}
+
+class errorTest extends PHPUnit_Framework_TestCase
+{
+ public function testException()
+ {
+ $this->setExpectedException('Exception', 'Bad token {{{foo}} ! Do you mean {{foo}} or {{{foo}}}?');
+ $php = LightnCandy::compile('{{{foo}}', Array('flags' => LightnCandy::FLAG_ERROR_EXCEPTION));
+ }
+
+ public function testErrorLog()
+ {
+ start_catch_error_log();
+ $php = LightnCandy::compile('{{{foo}}', Array('flags' => LightnCandy::FLAG_ERROR_LOG));
+ $e = stop_catch_error_log();
+ if ($e) {
+ $this->assertEquals(Array('Bad token {{{foo}} ! Do you mean {{foo}} or {{{foo}}}?'), $e);
+ } else {
+ $this->markTestIncomplete('skip HHVM');
+ }
+ }
+
+ /**
+ * @dataProvider renderErrorProvider
+ */
+ public function testRenderingException($test)
+ {
+ $this->setExpectedException('Exception', $test['expected']);
+ $php = LightnCandy::compile($test['template'], $test['options']);
+ $renderer = LightnCandy::prepare($php);
+ $renderer(null, LCRun3::DEBUG_ERROR_EXCEPTION);
+ }
+
+ /**
+ * @dataProvider renderErrorProvider
+ */
+ public function testRenderingErrorLog($test)
+ {
+ start_catch_error_log();
+ $php = LightnCandy::compile($test['template'], $test['options']);
+ $renderer = LightnCandy::prepare($php);
+ $renderer(null, LCRun3::DEBUG_ERROR_LOG);
+ $e = stop_catch_error_log();
+ if ($e) {
+ $this->assertEquals(Array($test['expected']), $e);
+ } else {
+ $this->markTestIncomplete('skip HHVM');
+ }
+ }
+
+ public function renderErrorProvider()
+ {
+ $errorCases = Array(
+ Array(
+ 'template' => '{{{foo}}}',
+ 'expected' => 'LCRun3: [foo] is not exist',
+ ),
+ Array(
+ 'template' => '{{foo}}',
+ 'options' => Array(
+ 'hbhelpers' => Array(
+ 'foo' => function () {
+ return 1/0;
+ }
+ ),
+ ),
+ 'expected' => 'LCRun3: call custom helper \'foo\' error: Division by zero',
+ ),
+ );
+
+ return array_map(function($i) {
+ if (!isset($i['options'])) {
+ $i['options'] = Array('flags' => LightnCandy::FLAG_RENDER_DEBUG);
+ }
+ if (!isset($i['options']['flags'])) {
+ $i['options']['flags'] = LightnCandy::FLAG_RENDER_DEBUG;
+ }
+ return Array($i);
+ }, $errorCases);
+ }
+
+ /**
+ * @dataProvider errorProvider
+ */
+ public function testErrors($test)
+ {
+ global $tmpdir;
+
+ $php = LightnCandy::compile($test['template'], $test['options']);
+ $context = LightnCandy::getContext();
+
+ // This case should be compiled without error
+ if (!isset($test['expected'])) {
+ return;
+ }
+
+ $this->assertEquals($test['expected'], $context['error'], "Code: $php");
+ }
+
+ public function errorProvider()
+ {
+ $errorCases = Array(
+ Array(
+ 'template' => '{{testerr1}}}',
+ 'expected' => 'Bad token {{testerr1}}} ! Do you mean {{testerr1}} or {{{testerr1}}}?',
+ ),
+ Array(
+ 'template' => '{{{testerr2}}',
+ 'expected' => 'Bad token {{{testerr2}} ! Do you mean {{testerr2}} or {{{testerr2}}}?',
+ ),
+ Array(
+ 'template' => '{{{#testerr3}}}',
+ 'expected' => 'Bad token {{{#testerr3}}} ! Do you mean {{#testerr3}} ?',
+ ),
+ Array(
+ 'template' => '{{{!testerr4}}}',
+ 'expected' => 'Bad token {{{!testerr4}}} ! Do you mean {{!testerr4}} ?',
+ ),
+ Array(
+ 'template' => '{{{^testerr5}}}',
+ 'expected' => 'Bad token {{{^testerr5}}} ! Do you mean {{^testerr5}} ?',
+ ),
+ Array(
+ 'template' => '{{{/testerr6}}}',
+ 'expected' => 'Bad token {{{/testerr6}}} ! Do you mean {{/testerr6}} ?',
+ ),
+ Array(
+ 'template' => '{{win[ner.test1}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming in {{win[ner.test1}}',
+ ),
+ Array(
+ 'template' => '{{win]ner.test2}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'win]ner.test2\' in {{win]ner.test2}} !',
+ ),
+ Array(
+ 'template' => '{{wi[n]ner.test3}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'wi[n]ner.test3\' in {{wi[n]ner.test3}} !',
+ ),
+ Array(
+ 'template' => '{{winner].[test4]}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'winner].[test4]\' in {{winner].[test4]}} !',
+ ),
+ Array(
+ 'template' => '{{winner[.test5]}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'winner[.test5]\' in {{winner[.test5]}} !',
+ ),
+ Array(
+ 'template' => '{{winner.[.test6]}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{winner.[#te.st7]}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{test8}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{test9]}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'test9]\' in {{test9]}} !',
+ ),
+ Array(
+ 'template' => '{{testA[}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'testA[\' in {{testA[}} !',
+ ),
+ Array(
+ 'template' => '{{[testB}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming in {{[testB}}',
+ ),
+ Array(
+ 'template' => '{{]testC}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \']testC\' in {{]testC}} !',
+ ),
+ Array(
+ 'template' => '{{[testD]}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{te]stE}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'te]stE\' in {{te]stE}} !',
+ ),
+ Array(
+ 'template' => '{{tee[stF}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming in {{tee[stF}}',
+ ),
+ Array(
+ 'template' => '{{te.e[stG}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming in {{te.e[stG}}',
+ ),
+ Array(
+ 'template' => '{{te.e]stH}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'te.e]stH\' in {{te.e]stH}} !',
+ ),
+ Array(
+ 'template' => '{{te.e[st.endI}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming in {{te.e[st.endI}}',
+ ),
+ Array(
+ 'template' => '{{te.e]st.endJ}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'te.e]st.endJ\' in {{te.e]st.endJ}} !',
+ ),
+ Array(
+ 'template' => '{{te.[est].endK}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{te.t[est].endL}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'te.t[est].endL\' in {{te.t[est].endL}} !',
+ ),
+ Array(
+ 'template' => '{{te.t[est]o.endM}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'te.t[est]o.endM\' in {{te.t[est]o.endM}} !',
+ ),
+ Array(
+ 'template' => '{{te.[est]o.endN}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ 'expected' => 'Wrong variable naming as \'te.[est]o.endN\' in {{te.[est]o.endN}} !',
+ ),
+ Array(
+ 'template' => '{{te.[e.st].endO}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{te.[e.s[t].endP}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{te.[e[s.t].endQ}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ADVARNAME),
+ ),
+ Array(
+ 'template' => '{{helper}}',
+ 'options' => Array('helpers' => Array(
+ 'helper' => Array('bad input'),
+ )),
+ 'expected' => 'I found an array in helpers with key as helper, please fix it.',
+ ),
+ Array(
+ 'template' => '<ul>{{#each item}}<li>{{name}}</li>',
+ 'expected' => 'Unclosed token {{{#each item}}} !!',
+ ),
+ Array(
+ 'template' => 'issue63: {{test_join}} Test! {{this}} {{/test_join}}',
+ 'expected' => 'Unexpect token: {{/test_join}} !',
+ ),
+ Array(
+ 'template' => '{{#if a}}TEST{{/with}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH),
+ 'expected' => 'Unexpect token: {{/with}} !',
+ ),
+ Array(
+ 'template' => '{{#foo}}error{{/bar}}',
+ 'expected' => 'Unexpect token {{/bar}} ! Previous token {{#[foo]}} is not closed',
+ ),
+ Array(
+ 'template' => '{{../foo}}',
+ 'expected' => 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag',
+ ),
+ Array(
+ 'template' => '{{..}}',
+ 'expected' => 'Do not support {{../var}}, you should do compile with LightnCandy::FLAG_PARENT flag',
+ ),
+ Array(
+ 'template' => '{{test_join [a]=b}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_NAMEDARG,
+ 'helpers' => Array('test_join')
+ ),
+ 'expected' => "Wrong argument name as '[a]' in {{test_join [a]=b}} ! You should fix your template or compile with LightnCandy::FLAG_ADVARNAME flag.",
+ ),
+ Array(
+ 'template' => '{{a=b}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_NAMEDARG),
+ 'expected' => 'Do not support name=value in {{a=b}}, you should use it after a custom helper.',
+ ),
+ Array(
+ 'template' => '{{test a=b}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_NAMEDARG),
+ 'expected' => 'Do not support name=value in {{test a=b}}, maybe you missing the custom helper?',
+ ),
+ Array(
+ 'template' => '{{#test a=b}}YA~{{/test}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_NAMEDARG),
+ 'expected' => 'Do not support name=value in {{#test a=b}}, maybe you missing the block custom helper?',
+ ),
+ Array(
+ 'template' => '{{#foo}}1{{^}}2{{/foo}}',
+ 'expected' => 'Do not support {{^}}, you should do compile with LightnCandy::FLAG_ELSE flag',
+ ),
+ Array(
+ 'template' => '{{#with a}OK!{{/with}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH),
+ 'expected' => 'Unclosed token {{{#with a}OK!{{/with}}} !!',
+ ),
+ Array(
+ 'template' => '{{#each a}OK!{{/each}}',
+ 'expected' => 'Unclosed token {{{#each a}OK!{{/each}}} !!',
+ ),
+ Array(
+ 'template' => '{{#with items}}OK!{{/with}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH),
+ ),
+ Array(
+ 'template' => '{{#with}}OK!{{/with}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH),
+ 'expected' => 'No argument after {{#with}} !',
+ ),
+ Array(
+ 'template' => '{{>not_found}}',
+ 'expected' => "Can not find partial file for 'not_found', you should set correct basedir and fileext in options",
+ ),
+ Array(
+ 'template' => '{{>tests/test1 foo}}',
+ 'options' => Array('basedir' => '.'),
+ 'expected' => 'Do not support {{>tests/test1 [foo]}}, you should do compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag',
+ ),
+ Array(
+ 'template' => '{{#with foo}}ABC{{/with}}',
+ 'expected' => 'Do not support {{#with var}}, you should do compile with LightnCandy::FLAG_WITH flag',
+ ),
+ Array(
+ 'template' => '{{abc}}',
+ 'options' => Array('helpers' => Array('abc')),
+ 'expected' => 'Can not find custom helper function defination abc() !',
+ ),
+ Array(
+ 'template' => '{{=~= =~=}}',
+ 'expected' => "Can not set delimiter contains '=' , you try to set delimiter as '~=' and '=~'.",
+ ),
+ Array(
+ 'template' => '{{>recursive}}',
+ 'options' => Array('basedir' => 'tests', 'flags' => LightnCandy::FLAG_WITH),
+ 'expected' => Array(
+ 'I found recursive partial includes as the path: recursive -> recursive! You should fix your template or compile with LightnCandy::FLAG_RUNTIMEPARTIAL flag.',
+ "Skip rendering partial 'recursive' again due to recursive detected",
+ )
+ ),
+ Array(
+ 'template' => '{{test_join (foo bar)}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_ADVARNAME,
+ 'helpers' => Array('test_join'),
+ ),
+ 'expected' => "Custom helper 'foo' not found!",
+ ),
+ );
+
+ return array_map(function($i) {
+ if (!isset($i['options'])) {
+ $i['options'] = Array('flags' => 0);
+ }
+ if (!isset($i['options']['flags'])) {
+ $i['options']['flags'] = 0;
+ }
+ if (isset($i['expected']) && !is_array($i['expected'])) {
+ $i['expected'] = Array($i['expected']);
+ }
+ return Array($i);
+ }, $errorCases);
+ }
+}
+
+
+?>
diff --git a/vendor/zordius/lightncandy/tests/example_debug.php b/vendor/zordius/lightncandy/tests/example_debug.php
new file mode 100644
index 00000000..bc573e0d
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/example_debug.php
@@ -0,0 +1,28 @@
+<?php
+require('src/lightncandy.php');
+
+$template = "Hello! {{name}} is {{gender}}.
+Test1: {{@root.name}}
+Test2: {{@root.gender}}
+Test3: {{../test3}}
+Test4: {{../../test4}}
+Test5: {{../../.}}
+Test6: {{../../[test'6]}}
+{{#each .}}
+each Value: {{.}}
+{{/each}}
+{{#.}}
+section Value: {{.}}
+{{/.}}
+{{#if .}}IF OK!{{/if}}
+{{#unless .}}Unless not OK!{{/unless}}
+";
+
+$php = LightnCandy::compile($template, Array(
+ 'flags' => LightnCandy::FLAG_RENDER_DEBUG | LightnCandy::FLAG_HANDLEBARSJS
+));
+
+$renderer = LightnCandy::prepare($php);
+error_reporting(0);
+echo $renderer(Array('name' => 'John'), LCRun3::DEBUG_TAGS_ANSI);
+?>
diff --git a/vendor/zordius/lightncandy/tests/example_helpers.php b/vendor/zordius/lightncandy/tests/example_helpers.php
new file mode 100644
index 00000000..f5e67d6f
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/example_helpers.php
@@ -0,0 +1,58 @@
+<?php
+
+// Custom Helper Interface ... noname arguments
+// Template: {{helper1 article.url article.text}}
+function helper1 ($args, $named) {
+ $u = (isset($args[0])) ? $args[0] : 'undefined';
+ $t = (isset($args[1])) ? $args[1] : 'undefined';
+ return "<a href=\"{$u}\">{$t}</a>";
+}
+
+// Custom Helper Interface ... named arguments
+// Template: {{helper1 url=article.url text=article.text [ur"l]=article.extra}}
+function helper2 ($args, $named) {
+ $u = isset($named['url']) ? jsraw($named['url']) : 'undefined';
+ $t = isset($named['text']) ? jsraw($named['text']) : 'undefined';
+ $x = isset($named['ur"l']) ? $named['ur"l'] : 'undefined';
+ return "<a href=\"{$u}\">{$t}</a>({$x})";
+}
+
+// Block Custom Helper Interface ...
+// Template: {{helper3 articles}}
+function helper3 ($cx, $args, $named) {
+ return Array('test1', 'test2', 'test3');
+}
+
+// Block Custom Helper Interface ...
+// Template: {{helper3 val=values odd=enable_odd}}
+function helper4 ($cx, $args, $named) {
+ if (isset($named['val']) && is_array($cx)) {
+ $cx['helper4_value'] = $named['val'] % 2;
+ return $cx;
+ }
+ if (isset($named['odd'])) {
+ return Array(1,3,5,7,9);
+ }
+}
+
+// Handlebars.js Custom Helper Interface ...
+// Template: {{#myeach articles}}Article: ....{{/myeach}}
+function myeach ($list, $options) {
+ foreach ($list as $item) {
+ $ret .= $options['fn']($item);
+ }
+ return $ret;
+}
+
+// Simulate Javascript toString() behaviors
+function jsraw ($i) {
+ if ($i === true) {
+ return 'true';
+ }
+ if ($i === false) {
+ return 'false';
+ }
+ return $i;
+}
+
+?>
diff --git a/vendor/zordius/lightncandy/tests/handlebarsSpecTest.php b/vendor/zordius/lightncandy/tests/handlebarsSpecTest.php
new file mode 100644
index 00000000..5124b226
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/handlebarsSpecTest.php
@@ -0,0 +1,162 @@
+<?php
+
+require_once('src/lightncandy.php');
+
+$tmpdir = sys_get_temp_dir();
+
+class HandlebarsSpecTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider jsonSpecProvider
+ */
+ public function testSpecs($spec)
+ {
+ global $tmpdir;
+
+ //// Skip bad specs
+ // No expect in spec
+ if (!isset($spec['expected'])) {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , no expected result in spec, skip.");
+ }
+ // This spec is bad , lightncandy result as '} hello }' and same with mustache.js
+ if ($spec['template'] === '{{{{raw}}}} {{test}} {{{{/raw}}}}') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , bad spec, skip.");
+ }
+ // missing partial in this spec
+ if ($spec['it'] === 'rendering function partial in vm mode') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , bad spec, skip.");
+ }
+ // Helper depend on an external class, skip it now.
+ if ($spec['it'] === 'simple literals work') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , external class not found, skip.");
+ }
+ // partial not found: global_test
+ if ($spec['message'] === 'Partials can use globals or passed') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , partial not found, skip.");
+ }
+ // lambda not found in spec
+ if ($spec['it'] === "bug reported by @fat where lambdas weren't being properly resolved") {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , lambda not found, skip.");
+ }
+
+ //// Skip unsupported features
+ // can not get any hint of 'function' from handlebars-spec , maybe it is a conversion error.
+ if (($spec['description'] === 'basic context') && preg_match('/functions/', $spec['it'])) {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , undefined function in spec json, skip.");
+ }
+ if (preg_match('/(.+) with function argument/', $spec['it'])) {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , undefined function in spec json, skip.");
+ }
+ if ($spec['it'] === 'Functions are bound to the context in knownHelpers only mode') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , undefined function in spec json, skip.");
+ }
+ if ($spec['it'] === 'lambdas are resolved by blockHelperMissing, not handlebars proper') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , undefined function in spec json, skip.");
+ }
+ if ($spec['description'] === '#SafeString') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , undefined function in spec json, skip.");
+ }
+
+ // Do not support includeZero now
+ if (($spec['description'] === '#if') && preg_match('/includeZero=true/', $spec['template'])) {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , lightncandy do not support this now.");
+ }
+
+ // Do not support setting options.data now
+ if ($spec['it'] === 'data passed to helpers') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , lightncandy do not support this now.");
+ }
+
+ // Do not support buildin helper : lookup now
+ if ($spec['description'] == '#lookup') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , lightncandy do not support this now.");
+ }
+
+ // Lightncandy will not support old path style as foo/bar , now only support foo.bar .
+ if ($spec['it'] === 'literal paths') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , lightncandy do not support this now.");
+ }
+
+ // Do not support {{~{foo}~}} , use {{{~foo~}}}
+ if ($spec['template'] === ' {{~{foo}~}} ') {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , lightncandy do not support this now.");
+ }
+
+ // setup helpers
+ $helpers = Array();
+ if (isset($spec['helpers'])) {
+ foreach ($spec['helpers'] as $name => $func) {
+ if (!isset($func['php'])) {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , no PHP helper code provided for this case.");
+ }
+
+ // Wrong PHP helper interface in spec, skip.
+ preg_match('/function\s*\(.+?\)/', isset($func['javascript']) ? $func['javascript'] : '', $js_args);
+ preg_match('/function\s*\(.+?\)/', $func['php'], $php_args);
+ $jsn = isset($js_args[0]) ? substr_count($js_args[0], ',') : 0;
+ $phpn = isset($php_args[0]) ? substr_count($php_args[0], ',') : 0;
+ if ($jsn !== $phpn) {
+ $this->markTestIncomplete("Skip [{$spec['file']}#{$spec['description']}]#{$spec['no']} , PHP helper interface is wrong.");
+ }
+
+ $hname = "custom_helper_{$spec['no']}_$name";
+ $helpers[$name] = $hname;
+ eval(preg_replace('/function/', "function $hname", $func['php'], 1));
+ }
+
+ }
+
+ if (($spec['it'] === 'tokenizes hash arguments') || ($spec['it'] === 'tokenizes special @ identifiers')) {
+ $helpers['foo'] = function () {return 'ABC';};
+ }
+
+ $flag = LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_ERROR_EXCEPTION | LightnCandy::FLAG_RUNTIMEPARTIAL | LightnCandy::FLAG_EXTHELPER | LightnCandy::FLAG_ERROR_SKIPPARTIAL | LightnCandy::FLAG_EXTHELPER;
+
+ foreach (Array($flag, $flag | LightnCandy::FLAG_STANDALONE) as $f) {
+ try {
+ $php = LightnCandy::compile($spec['template'], Array(
+ 'flags' => $f,
+ 'hbhelpers' => $helpers,
+ 'basedir' => $tmpdir,
+ 'partials' => isset($spec['partials']) ? $spec['partials'] : null,
+ ));
+ } catch (Exception $e) {
+ if (($spec['description'] === 'Tokenizer') && preg_match('/tokenizes inverse .+ as "OPEN_INVERSE.+CLOSE"/', $spec['it'])) {
+ continue;
+ }
+ print_r(LightnCandy::getContext());
+ $this->fail('Exception:' . $e->getMessage());
+ }
+ $renderer = LightnCandy::prepare($php);
+ if ($spec['description'] === 'Tokenizer') {
+ // no compile error means passed
+ continue;
+ }
+ $this->assertEquals($spec['expected'], $renderer($spec['data']), "[{$spec['file']}#{$spec['description']}]#{$spec['no']}:{$spec['it']} PHP CODE: $php");
+ }
+ }
+
+ public function jsonSpecProvider()
+ {
+ $ret = Array();
+
+ foreach (glob('specs/handlebars/spec/*.json') as $file) {
+ $i=0;
+ $json = json_decode(file_get_contents($file), true);
+ $ret = array_merge($ret, array_map(function ($d) use ($file, &$i) {
+ $d['file'] = $file;
+ $d['no'] = ++$i;
+ if (!isset($d['message'])) {
+ $d['message'] = null;
+ }
+ if (!isset($d['data'])) {
+ $d['data'] = null;
+ }
+ return Array($d);
+ }, $json));
+ }
+
+ return $ret;
+ }
+}
+?>
diff --git a/vendor/zordius/lightncandy/tests/helpers_for_test.php b/vendor/zordius/lightncandy/tests/helpers_for_test.php
new file mode 100644
index 00000000..93a1dbc7
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/helpers_for_test.php
@@ -0,0 +1,137 @@
+<?php
+
+// Classes for inputs or helpers
+class myClass {
+ function test() {
+ return 'testMethod OK!';
+ }
+
+ function helper2($arg) {
+ return "=$arg=";
+ }
+
+ function __call($method, $args) {
+ return "-- $method:" . print_r($args, true);
+ }
+}
+
+class foo {
+ public $prop = 'Yes!';
+
+ function bar() {
+ return 'OK!';
+ }
+}
+
+class twoDimensionIterator implements Iterator {
+ private $position = 0;
+ private $x = 0;
+ private $y = 0;
+ private $w = 0;
+ private $h = 0;
+
+ public function __construct($w, $h) {
+ $this->w = $w;
+ $this->h = $h;
+ $this->rewind();
+ }
+
+ function rewind() {
+ $this->position = 0;
+ $this->x = 0;
+ $this->y = 0;
+ }
+
+ function current() {
+ return $this->x * $this->y;
+ }
+
+ function key() {
+ return $this->x . 'x' . $this->y;
+ }
+
+ function next() {
+ ++$this->position;
+ $this->x = $this->position % $this->w;
+ $this->y = floor($this->position / $this->w);
+ }
+
+ function valid() {
+ return $this->position < $this->w * $this->h;
+ }
+}
+
+// Custom helpers
+function helper1($arg) {
+ return "-$arg-";
+}
+function alink($u, $t) {
+ return "<a href=\"$u\">$t</a>";
+}
+
+ function meetup_date_format() {
+ return "OKOK~1";
+}
+
+function meetup_date_format2() {
+ return "OKOK~2";
+}
+
+function meetup_date_format3 () {
+ return "OKOK~3";
+}
+
+function meetup_date_format4(){
+ return "OKOK~4";};
+
+
+function test_array ($input) {
+ return is_array($input[0]) ? 'IS_ARRAY' : 'NOT_ARRAY';
+}
+
+function test_join ($input) {
+ return join('.', $input[0]);
+}
+
+// Custom helpers for handlebars (should be used in hbhelpers)
+function myif ($conditional, $options) {
+ if ($conditional) {
+ return $options['fn']();
+ } else {
+ return $options['inverse']();
+ }
+}
+
+function mywith ($context, $options) {
+ return $options['fn']($context);
+}
+
+function myeach ($context, $options) {
+ $ret = '';
+ foreach ($context as $cx) {
+ $ret .= $options['fn']($cx);
+ }
+ return $ret;
+}
+
+function mylogic ($input, $yes, $no, $options) {
+ if ($input === true) {
+ return $options['fn']($yes);
+ } else {
+ return $options['inverse']($no);
+ }
+}
+
+function mydash ($a, $b) {
+ return "$a-$b";
+}
+
+function myjoin ($a, $b) {
+ return "$a$b";
+}
+
+function getroot ($options) {
+ return $options['data']['root'];
+}
+
+?>
diff --git a/vendor/zordius/lightncandy/tests/mustacheSpecTest.php b/vendor/zordius/lightncandy/tests/mustacheSpecTest.php
new file mode 100644
index 00000000..158f82cf
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/mustacheSpecTest.php
@@ -0,0 +1,53 @@
+<?php
+
+require_once('src/lightncandy.php');
+
+$tmpdir = sys_get_temp_dir();
+
+class MustacheSpecTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider jsonSpecProvider
+ */
+ public function testSpecs($spec)
+ {
+ global $tmpdir;
+
+ $flag = LightnCandy::FLAG_MUSTACHE | LightnCandy::FLAG_ERROR_EXCEPTION | LightnCandy::FLAG_RUNTIMEPARTIAL;
+
+ foreach (Array($flag, $flag | LightnCandy::FLAG_STANDALONE) as $f) {
+ $php = LightnCandy::compile($spec['template'], Array(
+ 'flags' => $f,
+ 'partials' => isset($spec['partials']) ? $spec['partials'] : null,
+ 'basedir' => $tmpdir,
+ ));
+ $renderer = LightnCandy::prepare($php);
+ $this->assertEquals($spec['expected'], $renderer($spec['data']), "[{$spec['file']}.{$spec['name']}]#{$spec['no']}:{$spec['desc']} PHP CODE: $php");
+ }
+ }
+
+ public function jsonSpecProvider()
+ {
+ $ret = Array();
+
+ foreach (glob('specs/mustache/specs/*.json') as $file) {
+ // Skip lambda extension
+ if (preg_match('/lambdas\\.json$/', $file)) {
+ continue;
+ }
+
+ $i=0;
+ $json = json_decode(file_get_contents($file), true);
+ $ret = array_merge($ret, array_map(function ($d) use ($file, &$i) {
+ $d['file'] = $file;
+ $d['no'] = ++$i;
+ return Array($d);
+ }, $json['tests']));
+ }
+
+ return $ret;
+ }
+}
+
+
+?>
diff --git a/vendor/zordius/lightncandy/tests/recursive.tmpl b/vendor/zordius/lightncandy/tests/recursive.tmpl
new file mode 100644
index 00000000..9e3290da
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/recursive.tmpl
@@ -0,0 +1 @@
+{{#if foo}}{{bar}} -> {{#with foo}}{{>recursive}}{{/with}}{{else}}END!{{/if}}
diff --git a/vendor/zordius/lightncandy/tests/regressionTest.php b/vendor/zordius/lightncandy/tests/regressionTest.php
new file mode 100644
index 00000000..bf591536
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/regressionTest.php
@@ -0,0 +1,736 @@
+<?php
+
+require_once('src/lightncandy.php');
+require_once('tests/helpers_for_test.php');
+
+$tmpdir = sys_get_temp_dir();
+
+class regressionTest extends PHPUnit_Framework_TestCase
+{
+ /**
+ * @dataProvider issueProvider
+ */
+ public function testIssues($issue)
+ {
+ global $tmpdir;
+
+ $php = LightnCandy::compile($issue['template'], isset($issue['options']) ? $issue['options'] : null);
+ $context = LightnCandy::getContext();
+ if (count($context['error'])) {
+ $this->fail('Compile failed due to:' . print_r($context['error'], true));
+ }
+ $renderer = LightnCandy::prepare($php);
+
+ $this->assertEquals($issue['expected'], $renderer($issue['data'], $issue['debug']), "PHP CODE:\n$php");
+ }
+
+ public function issueProvider()
+ {
+ $issues = Array(
+ Array(
+ 'id' => 39,
+ 'template' => '{{{tt}}}',
+ 'options' => null,
+ 'data' => Array('tt' => 'bla bla bla'),
+ 'expected' => 'bla bla bla'
+ ),
+
+ Array(
+ 'id' => 44,
+ 'template' => '<div class="terms-text"> {{render "artists-terms"}} </div>',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_EXTHELPER,
+ 'helpers' => Array(
+ 'url',
+ 'render' => function($view,$data = array()) {
+ return 'OK!';
+ }
+ )
+ ),
+ 'data' => Array('tt' => 'bla bla bla'),
+ 'expected' => '<div class="terms-text"> OK! </div>'
+ ),
+
+ Array(
+ 'id' => 45,
+ 'template' => '{{{a.b.c}}}, {{a.b.bar}}, {{a.b.prop}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_INSTANCE | LightnCandy::FLAG_HANDLEBARSJS,
+ ),
+ 'data' => Array('a' => Array('b' => new foo)),
+ 'expected' => ', OK!, Yes!'
+ ),
+
+ Array(
+ 'id' => 46,
+ 'template' => '{{{this.id}}}, {{a.id}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_THIS,
+ ),
+ 'data' => Array('id' => 'bla bla bla', 'a' => Array('id' => 'OK!')),
+ 'expected' => 'bla bla bla, OK!'
+ ),
+
+ Array(
+ 'id' => 49,
+ 'template' => '{{date_format}} 1, {{date_format2}} 2, {{date_format3}} 3, {{date_format4}} 4',
+ 'options' => Array(
+ 'helpers' => Array(
+ 'date_format' => 'meetup_date_format',
+ 'date_format2' => 'meetup_date_format2',
+ 'date_format3' => 'meetup_date_format3',
+ 'date_format4' => 'meetup_date_format4'
+ )
+ ),
+ 'data' => null,
+ 'expected' => 'OKOK~1 1, OKOK~2 2, OKOK~3 3, OKOK~4 4'
+ ),
+
+ Array(
+ 'id' => 52,
+ 'template' => '{{{test_array tmp}}} should be happy!',
+ 'options' => Array(
+ 'helpers' => Array(
+ 'test_array',
+ )
+ ),
+ 'data' => Array('tmp' => Array('A', 'B', 'C')),
+ 'expected' => 'IS_ARRAY should be happy!'
+ ),
+
+ Array(
+ 'id' => 62,
+ 'template' => '{{{test_join @root.foo.bar}}} should be happy!',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_ERROR_EXCEPTION,
+ 'helpers' => array('test_join')
+ ),
+ 'data' => Array('foo' => Array('A', 'B', 'bar' => Array('C', 'D'))),
+ 'expected' => 'C.D should be happy!',
+ ),
+
+ Array(
+ 'id' => 64,
+ 'template' => '{{#each foo}} Test! {{this}} {{/each}}{{> test1}} ! >>> {{>recursive}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_RUNTIMEPARTIAL,
+ 'basedir' => 'tests',
+ ),
+ 'data' => Array(
+ 'bar' => 1,
+ 'foo' => Array(
+ 'bar' => 3,
+ 'foo' => Array(
+ 'bar' => 5,
+ 'foo' => Array(
+ 'bar' => 7,
+ 'foo' => Array(
+ 'bar' => 11,
+ 'foo' => Array(
+ 'no foo here'
+ )
+ )
+ )
+ )
+ )
+ ),
+ 'expected' => " Test! 3 Test! [object Object] 123\n ! >>> 1 -> 3 -> 5 -> 7 -> 11 -> END!\n\n\n\n\n\n",
+ ),
+
+ Array(
+ 'id' => 66,
+ 'template' => '{{&foo}} , {{foo}}, {{{foo}}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS
+ ),
+ 'data' => Array('foo' => 'Test & " \' :)'),
+ 'expected' => 'Test & " \' :) , Test &amp; &quot; &#x27; :), Test & " \' :)',
+ ),
+
+ Array(
+ 'id' => 68,
+ 'template' => '{{#myeach foo}} Test! {{this}} {{/myeach}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'hbhelpers' => Array(
+ 'myeach' => function ($context, $options) {
+ $ret = '';
+ foreach ($context as $cx) {
+ $ret .= $options['fn']($cx);
+ }
+ return $ret;
+ }
+ )
+ ),
+ 'data' => Array('foo' => Array('A', 'B', 'bar' => Array('C', 'D', 'E'))),
+ 'expected' => ' Test! A Test! B Test! C,D,E ',
+ ),
+
+ Array(
+ 'id' => 81,
+ 'template' => '{{#with ../person}} {{^name}} Unknown {{/name}} {{/with}}?!',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_ERROR_EXCEPTION,
+ ),
+ 'data' => Array('parent?!' => Array('A', 'B', 'bar' => Array('C', 'D', 'E'))),
+ 'expected' => '?!'
+ ),
+
+ Array(
+ 'id' => 83,
+ 'template' => '{{> tests/test1}}',
+ 'options' => Array(
+ 'basedir' => '.',
+ ),
+ 'data' => null,
+ 'expected' => "123\n"
+ ),
+
+ Array(
+ 'id' => 85,
+ 'template' => '{{helper 1 foo bar="q"}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'hbhelpers' => Array(
+ 'helper' => function ($arg1, $arg2, $options) {
+ return "ARG1:$arg1, ARG2:$arg2, HASH:{$options['hash']['bar']}";
+ }
+ )
+ ),
+ 'data' => Array('foo' => 'BAR'),
+ 'expected' => 'ARG1:1, ARG2:BAR, HASH:q',
+ ),
+
+ Array(
+ 'id' => 88,
+ 'template' => '{{>test2}}',
+ 'options' => Array(
+ 'flags' => 0,
+ 'basedir' => 'tests',
+ ),
+ 'data' => null,
+ 'expected' => "a123\nb\n",
+ ),
+
+ Array(
+ 'id' => 89,
+ 'template' => '{{#with}}SHOW:{{.}} {{/with}}',
+ 'data' => Array('with' => Array(1, 3, 7), 'a' => Array(2, 4, 9)),
+ 'expected' => 'SHOW:1 SHOW:3 SHOW:7 ',
+ ),
+
+ Array(
+ 'id' => 90,
+ 'template' => '{{#items}}{{#value}}{{.}}{{/value}}{{/items}}',
+ 'data' => Array('items' => Array(Array('value'=>'123'))),
+ 'expected' => '123',
+ ),
+
+ Array(
+ 'id' => 109,
+ 'template' => '{{#if "OK"}}it\'s great!{{/if}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_NOESCAPE,
+ ),
+ 'data' => null,
+ 'expected' => 'it\'s great!',
+ ),
+
+ Array(
+ 'id' => 110,
+ 'template' => 'ABC{{#block "YES!"}}DEF{{foo}}GHI{{/block}}JKL',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_BESTPERFORMANCE,
+ 'hbhelpers' => Array(
+ 'block' => function ($name, $options) {
+ return "1-$name-2-" . $options['fn']() . '-3';
+ }
+ ),
+ ),
+ 'data' => Array('foo' => 'bar'),
+ 'expected' => 'ABC1-YES!-2-DEFbarGHI-3JKL',
+ ),
+
+ Array(
+ 'id' => 109,
+ 'template' => '{{foo}} {{> test}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_NOESCAPE,
+ 'partials' => Array('test' => '{{foo}}'),
+ ),
+ 'data' => Array('foo' => '<'),
+ 'expected' => '< <',
+ ),
+
+ Array(
+ 'id' => 114,
+ 'template' => '{{^myeach .}}OK:{{.}},{{else}}NOT GOOD{{/myeach}}',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_BESTPERFORMANCE,
+ 'hbhelpers' => Array(
+ 'myeach' => function ($context, $options) {
+ $ret = '';
+ foreach ($context as $cx) {
+ $ret .= $options['fn']($cx);
+ }
+ return $ret;
+ }
+ ),
+ ),
+ 'data' => Array(1, 'foo', 3, 'bar'),
+ 'expected' => 'NOT GOODNOT GOODNOT GOODNOT GOOD',
+ ),
+
+ Array(
+ 'template' => 'ABC{{#block "YES!"}}DEF{{foo}}GHI{{else}}NO~{{/block}}JKL',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_BESTPERFORMANCE,
+ 'hbhelpers' => Array(
+ 'block' => function ($name, $options) {
+ return "1-$name-2-" . $options['fn']() . '-3';
+ }
+ ),
+ ),
+ 'data' => Array('foo' => 'bar'),
+ 'expected' => 'ABC1-YES!-2-DEFbarGHI-3JKL',
+ ),
+
+ Array(
+ 'template' => '-{{getroot}}=',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_SPVARS,
+ 'hbhelpers' => Array('getroot'),
+ ),
+ 'data' => 'ROOT!',
+ 'expected' => '-ROOT!=',
+ ),
+
+ Array(
+ 'template' => 'A{{#each .}}-{{#each .}}={{.}},{{@key}},{{@index}},{{@../index}}~{{/each}}%{{/each}}B',
+ 'data' => Array(Array('a' => 'b'), Array('c' => 'd'), Array('e' => 'f')),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_PARENT | LightnCandy::FLAG_THIS | LightnCandy::FLAG_SPVARS,
+ ),
+ 'expected' => 'A-=b,a,0,0~%-=d,c,0,1~%-=f,e,0,2~%B',
+ ),
+
+ Array(
+ 'template' => 'ABC{{#block "YES!"}}TRUE{{else}}DEF{{foo}}GHI{{/block}}JKL',
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_BESTPERFORMANCE,
+ 'hbhelpers' => Array(
+ 'block' => function ($name, $options) {
+ return "1-$name-2-" . $options['inverse']() . '-3';
+ }
+ ),
+ ),
+ 'data' => Array('foo' => 'bar'),
+ 'expected' => 'ABC1-YES!-2-DEFbarGHI-3JKL',
+ ),
+
+ Array(
+ 'template' => '{{#each .}}{{..}}>{{/each}}',
+ 'data' => Array('a', 'b', 'c'),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ ),
+ 'expected' => 'a,b,c>a,b,c>a,b,c>',
+ ),
+
+ Array(
+ 'template' => '{{#each .}}->{{>tests/test3}}{{/each}}',
+ 'data' => Array('a', 'b', 'c'),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'basedir' => '.',
+ ),
+ 'expected' => "->New context:a\n->New context:b\n->New context:c\n",
+ ),
+
+ Array(
+ 'template' => '{{#each .}}->{{>tests/test3 ../foo}}{{/each}}',
+ 'data' => Array('a', 'foo' => Array('d', 'e', 'f')),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS | LightnCandy::FLAG_RUNTIMEPARTIAL,
+ 'basedir' => '.',
+ ),
+ 'expected' => "->New context:d,e,f\n->New context:d,e,f\n",
+ ),
+
+ Array(
+ 'template' => '{{{"{{"}}}',
+ 'data' => null,
+ 'expected' => '{{',
+ ),
+
+ Array(
+ 'template' => '{{good_helper}}',
+ 'data' => null,
+ 'options' => Array(
+ 'helpers' => Array('good_helper' => 'foo::bar'),
+ ),
+ 'expected' => 'OK!',
+ ),
+
+ Array(
+ 'template' => '-{{.}}-',
+ 'options' => Array('flags' => LightnCandy::FLAG_THIS),
+ 'data' => 'abc',
+ 'expected' => '-abc-',
+ ),
+
+ Array(
+ 'template' => '-{{this}}-',
+ 'options' => Array('flags' => LightnCandy::FLAG_THIS),
+ 'data' => 123,
+ 'expected' => '-123-',
+ ),
+
+ Array(
+ 'template' => '{{#if .}}YES{{else}}NO{{/if}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_ELSE),
+ 'data' => true,
+ 'expected' => 'YES',
+ ),
+
+ Array(
+ 'template' => '{{foo}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_RENDER_DEBUG),
+ 'data' => Array('foo' => 'OK'),
+ 'expected' => 'OK',
+ ),
+
+ Array(
+ 'template' => '{{foo}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_RENDER_DEBUG),
+ 'debug' => LCRun3::DEBUG_TAGS_ANSI,
+ 'data' => Array('foo' => 'OK'),
+ 'expected' => pack('H*', '1b5b303b33326d7b7b5b666f6f5d7d7d1b5b306d'),
+ ),
+
+ Array(
+ 'template' => '{{foo}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_RENDER_DEBUG),
+ 'debug' => LCRun3::DEBUG_TAGS_HTML,
+ 'data' => null,
+ 'expected' => '<!--MISSED((-->{{[foo]}}<!--))-->',
+ ),
+
+ Array(
+ 'template' => '{{#foo}}OK{{/foo}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_RENDER_DEBUG),
+ 'debug' => LCRun3::DEBUG_TAGS_HTML,
+ 'data' => null,
+ 'expected' => '<!--MISSED((-->{{#[foo]}}<!--))--><!--SKIPPED--><!--MISSED((-->{{/[foo]}}<!--))-->',
+ ),
+
+ Array(
+ 'template' => '{{#foo}}OK{{/foo}}',
+ 'options' => Array('flags' => LightnCandy::FLAG_RENDER_DEBUG),
+ 'debug' => LCRun3::DEBUG_TAGS_ANSI,
+ 'data' => null,
+ 'expected' => pack('H*', '1b5b303b33316d7b7b235b666f6f5d7d7d1b5b306d1b5b303b33336d534b49505045441b5b306d1b5b303b33316d7b7b2f5b666f6f5d7d7d1b5b306d'),
+ ),
+
+ Array(
+ 'template' => '{{#myif foo}}YES{{else}}NO{{/myif}}',
+ 'data' => null,
+ 'options' => Array(
+ 'hbhelpers' => Array('myif'),
+ ),
+ 'expected' => 'NO',
+ ),
+
+ Array(
+ 'template' => '{{#myif foo}}YES{{else}}NO{{/myif}}',
+ 'data' => Array('foo' => 1),
+ 'options' => Array(
+ 'hbhelpers' => Array('myif'),
+ ),
+ 'expected' => 'YES',
+ ),
+
+ Array(
+ 'template' => '{{#mylogic 0 foo bar}}YES:{{.}}{{else}}NO:{{.}}{{/mylogic}}',
+ 'data' => Array('foo' => 'FOO', 'bar' => 'BAR'),
+ 'options' => Array(
+ 'hbhelpers' => Array('mylogic'),
+ ),
+ 'expected' => 'NO:BAR',
+ ),
+
+ Array(
+ 'template' => '{{#mylogic true foo bar}}YES:{{.}}{{else}}NO:{{.}}{{/mylogic}}',
+ 'data' => Array('foo' => 'FOO', 'bar' => 'BAR'),
+ 'options' => Array(
+ 'hbhelpers' => Array('mylogic'),
+ ),
+ 'expected' => 'YES:FOO',
+ ),
+
+ Array(
+ 'template' => '{{#mywith foo}}YA: {{name}}{{/mywith}}',
+ 'data' => Array('name' => 'OK?', 'foo' => Array('name' => 'OK!')),
+ 'options' => Array(
+ 'hbhelpers' => Array('mywith'),
+ ),
+ 'expected' => 'YA: OK!',
+ ),
+
+ Array(
+ 'template' => '{{mydash \'abc\' "dev"}}',
+ 'data' => Array('a' => 'a', 'b' => 'b', 'c' => Array('c' => 'c'), 'd' => 'd', 'e' => 'e'),
+ 'options' => Array(
+ 'hbhelpers' => Array('mydash'),
+ ),
+ 'expected' => 'abc-dev',
+ ),
+
+ Array(
+ 'template' => '{{mydash \'a b c\' "d e f"}}',
+ 'data' => Array('a' => 'a', 'b' => 'b', 'c' => Array('c' => 'c'), 'd' => 'd', 'e' => 'e'),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_ADVARNAME,
+ 'hbhelpers' => Array('mydash'),
+ ),
+ 'expected' => 'a b c-d e f',
+ ),
+
+ Array(
+ 'template' => '{{mydash "abc" (test_array 1)}}',
+ 'data' => Array('a' => 'a', 'b' => 'b', 'c' => Array('c' => 'c'), 'd' => 'd', 'e' => 'e'),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_ADVARNAME,
+ 'hbhelpers' => Array('mydash'),
+ 'helpers' => Array('test_array'),
+ ),
+ 'expected' => 'abc-NOT_ARRAY',
+ ),
+
+ Array(
+ 'template' => '{{mydash "abc" (myjoin a b)}}',
+ 'data' => Array('a' => 'a', 'b' => 'b', 'c' => Array('c' => 'c'), 'd' => 'd', 'e' => 'e'),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_ADVARNAME,
+ 'hbhelpers' => Array('mydash', 'myjoin'),
+ ),
+ 'expected' => 'abc-ab',
+ ),
+
+ Array(
+ 'template' => '{{#with people}}Yes , {{name}}{{else}}No, {{name}}{{/with}}',
+ 'data' => Array('people' => Array('name' => 'Peter'), 'name' => 'NoOne'),
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH),
+ 'expected' => 'Yes , Peter',
+ ),
+
+ Array(
+ 'template' => '{{#with people}}Yes , {{name}}{{else}}No, {{name}}{{/with}}',
+ 'data' => Array('name' => 'NoOne'),
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH),
+ 'expected' => 'No, NoOne',
+ ),
+
+ Array(
+ 'template' => <<<VAREND
+<ul>
+ <li>1. {{helper1 name}}</li>
+ <li>2. {{helper1 value}}</li>
+ <li>3. {{myClass::helper2 name}}</li>
+ <li>4. {{myClass::helper2 value}}</li>
+ <li>5. {{he name}}</li>
+ <li>6. {{he value}}</li>
+ <li>7. {{h2 name}}</li>
+ <li>8. {{h2 value}}</li>
+ <li>9. {{link name}}</li>
+ <li>10. {{link value}}</li>
+ <li>11. {{alink url text}}</li>
+ <li>12. {{{alink url text}}}</li>
+</ul>
+VAREND
+ ,
+ 'data' => Array('name' => 'John', 'value' => 10000, 'url' => 'http://yahoo.com', 'text' => 'You&Me!'),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_ERROR_LOG | LightnCandy::FLAG_HANDLEBARSJS,
+ 'helpers' => Array(
+ 'helper1',
+ 'myClass::helper2',
+ 'he' => 'helper1',
+ 'h2' => 'myClass::helper2',
+ 'link' => function ($arg) {
+ return "<a href=\"{$arg}\">click here</a>";
+ },
+ 'alink',
+ )
+ ),
+ 'expected' => <<<VAREND
+<ul>
+ <li>1. -Array-</li>
+ <li>2. -Array-</li>
+ <li>3. =Array=</li>
+ <li>4. =Array=</li>
+ <li>5. -Array-</li>
+ <li>6. -Array-</li>
+ <li>7. =Array=</li>
+ <li>8. =Array=</li>
+ <li>9. &lt;a href=&quot;Array&quot;&gt;click here&lt;/a&gt;</li>
+ <li>10. &lt;a href=&quot;Array&quot;&gt;click here&lt;/a&gt;</li>
+ <li>11. &lt;a href=&quot;Array&quot;&gt;Array&lt;/a&gt;</li>
+ <li>12. <a href="Array">Array</a></li>
+</ul>
+VAREND
+ ),
+
+ Array(
+ 'template' => '{{test.test}} == {{test.test3}}',
+ 'data' => Array('test' => new myClass()),
+ 'options' => Array('flags' => LightnCandy::FLAG_INSTANCE),
+ 'expected' => "testMethod OK! == -- test3:Array\n(\n)\n",
+ ),
+
+ Array(
+ 'template' => '{{test.test}} == {{test.bar}}',
+ 'data' => Array('test' => new foo()),
+ 'options' => Array('flags' => LightnCandy::FLAG_INSTANCE),
+ 'expected' => ' == OK!',
+ ),
+
+ Array(
+ 'template' => '{{#each foo}}{{@key}}: {{.}},{{/each}}',
+ 'data' => Array('foo' => Array(1,'a'=>'b',5)),
+ 'expected' => ': 1,: b,: 5,',
+ ),
+
+ Array(
+ 'template' => '{{#each foo}}{{@key}}: {{.}},{{/each}}',
+ 'data' => Array('foo' => Array(1,'a'=>'b',5)),
+ 'options' => Array('flags' => LightnCandy::FLAG_SPVARS),
+ 'expected' => '0: 1,a: b,1: 5,',
+ ),
+
+ Array(
+ 'template' => '{{#each foo}}{{@key}}: {{.}},{{/each}}',
+ 'data' => Array('foo' => new twoDimensionIterator(2, 3)),
+ 'options' => Array('flags' => LightnCandy::FLAG_SPVARS),
+ 'expected' => '0x0: 0,1x0: 0,0x1: 0,1x1: 1,0x2: 0,1x2: 2,',
+ ),
+
+ Array(
+ 'template' => " {{#foo}}\n {{name}}\n{{/foo}}\n ",
+ 'data' => Array('foo' => Array(Array('name' => 'A'),Array('name' => 'd'),Array('name' => 'E'))),
+ 'options' => Array('flags' => LightnCandy::FLAG_MUSTACHESP),
+ 'expected' => " A\n d\n E\n ",
+ ),
+
+ Array(
+ 'template' => "{{bar}}\n {{#foo}}\n {{name}}\n{{/foo}}\n ",
+ 'data' => Array('bar' => 'OK', 'foo' => Array(Array('name' => 'A'),Array('name' => 'd'),Array('name' => 'E'))),
+ 'options' => Array('flags' => LightnCandy::FLAG_MUSTACHESP),
+ 'expected' => "OK\n A\n d\n E\n ",
+ ),
+
+ Array(
+ 'template' => " {{#if foo}}\nYES\n{{else}}\nNO\n{{/if}}\n",
+ 'data' => null,
+ 'options' => Array('flags' => LightnCandy::FLAG_MUSTACHESP | LightnCandy::FLAG_ELSE),
+ 'expected' => "NO\n",
+ ),
+
+ Array(
+ 'template' => " {{#each foo}}\n{{@key}}: {{.}}\n{{/each}}\nDONE",
+ 'data' => Array('foo' => Array('a' => 'A', 'b' => 'BOY!')),
+ 'options' => Array('flags' => LightnCandy::FLAG_SPVARS | LightnCandy::FLAG_MUSTACHESP),
+ 'expected' => "a: A\nb: BOY!\nDONE",
+ ),
+
+ Array(
+ 'template' => "{{>test1}}\n {{>test1}}\nDONE\n",
+ 'data' => null,
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_MUSTACHESP | LightnCandy::FLAG_MUSTACHEPAIN,
+ 'partials' => Array('test1' => "1:A\n 2:B\n 3:C\n 4:D\n5:E\n"),
+ ),
+ 'expected' => "1:A\n 2:B\n 3:C\n 4:D\n5:E\n 1:A\n 2:B\n 3:C\n 4:D\n 5:E\nDONE\n",
+ ),
+
+ Array(
+ 'template' => "{{>test1}}\n {{>test1}}\nDONE\n",
+ 'data' => null,
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_MUSTACHESP,
+ 'partials' => Array('test1' => "1:A\n 2:B\n 3:C\n 4:D\n5:E\n"),
+ ),
+ 'expected' => "1:A\n 2:B\n 3:C\n 4:D\n5:E\n1:A\n 2:B\n 3:C\n 4:D\n5:E\nDONE\n",
+ ),
+
+ Array(
+ 'template' => "{{>test1}}\n {{>test1}}\nDONE\n",
+ 'data' => null,
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_MUSTACHESP | LightnCandy::FLAG_RUNTIMEPARTIAL,
+ 'partials' => Array('test1' => "1:A\n 2:B\n 3:C\n 4:D\n5:E\n"),
+ ),
+ 'expected' => "1:A\n 2:B\n 3:C\n 4:D\n5:E\n1:A\n 2:B\n 3:C\n 4:D\n5:E\nDONE\n",
+ ),
+
+ Array(
+ 'template' => "ST:\n{{#foo}}\n {{>test1}}\n{{/foo}}\nOK\n",
+ 'data' => Array('foo' => Array(1, 2)),
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_MUSTACHESP | LightnCandy::FLAG_MUSTACHEPAIN | LightnCandy::FLAG_HANDLEBARSJS,
+ 'partials' => Array('test1' => "1:A\n 2:B({{@index}})\n"),
+ ),
+ 'expected' => "ST:\n 1:A\n 2:B(0)\n 1:A\n 2:B(1)\nOK\n",
+ ),
+
+ Array(
+ 'template' => "A\n {{#if 1}} \n\na\n{{#with 2}}\n123\n\n{{/with}}\n{{/if}} \n \n\n456",
+ 'data' => null,
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH | LightnCandy::FLAG_MUSTACHESP),
+ 'expected' => "A\n\na\n123\n\n \n\n456",
+ ),
+
+ Array(
+ 'template' => "\n{{#with 1}}\n\n{{#with 1}}\nb\n\n{{/with}}\n{{/with}}\nC",
+ 'data' => null,
+ 'options' => Array('flags' => LightnCandy::FLAG_WITH | LightnCandy::FLAG_MUSTACHESP),
+ 'expected' => "\n\nb\n\nC",
+ ),
+
+ Array(
+ 'template' => ">{{helper1 \"===\"}}<",
+ 'data' => null,
+ 'options' => Array(
+ 'flags' => LightnCandy::FLAG_HANDLEBARSJS,
+ 'hbhelpers' => Array(
+ 'helper1',
+ )
+ ),
+ 'expected' => ">-===-<",
+ ),
+
+ Array(
+ 'template' => "{{foo}}",
+ 'data' => Array('foo' => 'A&B " \''),
+ 'options' => Array('flags' => LightnCandy::FLAG_NOESCAPE),
+ 'expected' => "A&B \" '",
+ ),
+
+ Array(
+ 'template' => "{{foo}}",
+ 'data' => Array('foo' => 'A&B " \''),
+ 'options' => null,
+ 'expected' => "A&amp;B &quot; &#039;",
+ ),
+ );
+
+ return array_map(function($i) {
+ if (!isset($i['debug'])) {
+ $i['debug'] = 0;
+ }
+ return Array($i);
+ }, $issues);
+ }
+}
+
+?>
diff --git a/vendor/zordius/lightncandy/tests/test1.tmpl b/vendor/zordius/lightncandy/tests/test1.tmpl
new file mode 100644
index 00000000..190a1803
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/test1.tmpl
@@ -0,0 +1 @@
+123
diff --git a/vendor/zordius/lightncandy/tests/test2.tmpl b/vendor/zordius/lightncandy/tests/test2.tmpl
new file mode 100644
index 00000000..df980f7e
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/test2.tmpl
@@ -0,0 +1 @@
+a{{> test1}}b
diff --git a/vendor/zordius/lightncandy/tests/test3.tmpl b/vendor/zordius/lightncandy/tests/test3.tmpl
new file mode 100644
index 00000000..0b4e6bb9
--- /dev/null
+++ b/vendor/zordius/lightncandy/tests/test3.tmpl
@@ -0,0 +1 @@
+New context:{{.}}