Wikipediýa
tkwiki
https://tk.wikipedia.org/wiki/Ba%C5%9F_Sahypa
MediaWiki 1.46.0-wmf.26
first-letter
Media
Ýörite
Çekişme
Ulanyjy
Ulanyjy çekişme
Wikipediýa
Wikipediýa çekişme
Faýl
Faýl çekişme
MediaWiki
MediaWiki çekişme
Şablon
Şablon çekişme
Ýardam
Ýardam çekişme
Kategoriýa
Kategoriýa çekişme
TimedText
TimedText talk
Module
Module talk
Event
Event talk
Harby güýçler inženerligi
0
12794
268905
232087
2026-05-01T00:37:36Z
InternetArchiveBot
21623
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.5
268905
wikitext
text/x-wiki
[[Faýl:RIAN archive 67318 Soviet sappers.jpg|thumb|150px|Sapýorlar territoriýany howpsuzlaştyrýarlar.]]
[[Faýl:RIAN archive 734015 Wildfires in Moscow Region's Lukhovitsky district.jpg|thumb|]]
[[Faýl:Pioniere_beim_Brueckenbau_1.jpg|thumb|Harby güýçler inženerleri]]
[[Faýl:Slovakia Town Presov 270.jpg|thumb|Harby güýçler inženerleri]]
== Daşarky çykgytlar ==
* [http://structure.mil.ru/structure/forces/ground/structure/engineers.htm О Инженерных войсках на офиц. сайте министерства обороны РФ] {{Webarchive|url=https://web.archive.org/web/20130421070846/http://structure.mil.ru/structure/forces/ground/structure/engineers.htm |date=2013-04-21 }}
* [http://army.armor.kiev.ua/engenear/index.shtml Инженерные войска: История, штаты, техника, боеприпасы] {{Webarchive|url=https://web.archive.org/web/20140701064142/http://army.armor.kiev.ua/engenear/index.shtml |date=2014-07-01 }}
* [http://www.victory.mil.ru/lib/books/h/engineers/index.html Инженерные войска в боях за Советскую Родину] {{Webarchive|url=https://web.archive.org/web/20090625064204/http://www.victory.mil.ru/lib/books/h/engineers/index.html |date=2009-06-25 }}
* [http://Sapper-Museum.narod.ru Sapper Museum]
* [https://archive.is/20121225161605/zielenski.narod.ru/engineering-history.html Статья 310 лет ИВ России]
{{Inženerlik}}
[[Kategoriýa:Bilim]]
[[Kategoriýa:Bilimiñ Görnüşleri]]
[[Kategoriýa:Amaly ylymlar]]
[[Kategoriýa:Inženerlik]]
{{Inženerlik-ownuk}}
{{bilim-ownuk}}
{{ylym-ownuk}}
{{askeriye-ownuk}}
57rcdhjov4wu50u013vzsbjdm22wv72
Chymopapain Injection
0
19181
268916
255632
2026-05-01T02:17:35Z
Wooze
24525
Requesting deletion ([[:m:Special:MyLanguage/User:TenWhile6/XReport|XReport]] v3.1b)
268916
wikitext
text/x-wiki
<noinclude>{{delete|Spam <small>[[:m:Special:MyLanguage/User:TenWhile6/XReport|XReport]]</small>}}</noinclude>
== Diskiň gerniasiýasy üçin himonukleoliz (Himopapain sanjym) ==
Himonukleoliz(Iňlis dilinde [https://discectomy.net/chemonucleolysis-chymopapain-injection-for-disc-herniation/ Chymopapain Injection] {{Webarchive|url=https://web.archive.org/web/20210719103631/https://discectomy.net/chemonucleolysis-chymopapain-injection-for-disc-herniation/ |date=2021-07-19 }} diýilýär), Europeewropada Demirgazyk Amerikadan has meşhur, ýadro pulposusyny eritmek üçin ferment (papaýa ösümliginden alnan himopapain), oňurgara diskine iňňe we rentgen görkezmesi arkaly girizilýär. Hirurgiki disektomiýa üçin az täsir ediji alternatiwa hasaplanýar.
Oňurgaly disk gaty daşky gabykdan ýa-da annulus fibrozdan we ýadro pulposusy diýilýän ýumşak içki ýadrodan durýar. Daşky gabygyň gowşak ýeri ýadronyň köpelmegine ýa-da herni bolmagyna ýol açyp, goňşy nerw köklerine basyş we gahar-gazap döredip biler. Bu, köplenç aýagyň agyrmagyna ýa-da siyatikanyň döremegine sebäp bolýar, belki gowşaklygyň ýitmegi bilen baglanyşyklydyr. Himonukleoliz düşünjesi, sanjylýan ferment guşaklarynyň ýadrosy bolup, herni peseldýär we şeýlelik bilen nerw basyşydyr.
=== Netijelilik ===
Himiukleolizi kesilen diski bejermek hökmünde ulanmak 1993-nji ýylda ýüze çykan kynçylyklar barada hasabat çap edilenden soň çalt azaldy. Şeýle-de bolsa, tehnikanyň kämilleşdirilmegi we hassalaryň ýaramlylygy barada has anyk görkezmeler soňky ýyllarda uly goldaw bilen kabul edildi kliniki gözlegler. Himonukleoliz bilen iň gowularyny ýerine ýetirýän ýaly görünýän hassalar, gysga möhletli, aýakda agdyklyk edýän we şekillendirişde gowy lokallaşdyrylan korrelýasiýa disk patologiýasy bilen ýaşdyr (MRI ýa-da KT skaner). Sanjymdan soň derrew köp hassalar agyrylaryndan birneme ýeňillik alýarlar, ýöne köpüsi 6 hepde töweregi doly peýdany görmeýärler. Degişli saýlanan adamlarda habar berlen netijeler, 5 ýyldan 20 ýyla çenli dowam edýän 75-80% agyryny azaltmak bilen operasiýa bilen deňeşdirilýär. Çylşyrymlylyk derejesi hirurgiki disektomiýadan 5-10 esse az diýilýär.
2000-nji ýylda Sinaý dagynyň Medisina Journalurnalynyň gözden geçirişinde “himopapain ulanýan himonukleoliz, herni lomber pulposusyny bejermek üçin ulanylýan iň az invaziv usuldyr. 37 ýyllyk kliniki tejribeden, birnäçe kliniki synagdan, Azyk we neşe serişdeleriniň müdirligi tarapyndan berlen iki merkezli gözlegden we ylmy jemgyýetde gyzgalaňly jedellerden soň, herni diskleri bejermek üçin himopapain sanjymynyň (degişli saýlanan hassalarda) subut edildi. az kynçylyklar we ep-esli tygşytlamagyň artykmaçlygy bilen laminektomiýa ýaly üstünlikli. ”
Howpsuzlyk: Himopapain ulanyp, himonukleoliziň belli bir garşylyklary bar, hususan-da ilatyň 0,3% -de bar bolan papaýa allergiýasy (anafilaksiýa diýilýän allergiki reaksiýa), bu amaldan öň barlanmalydyr.
==== Himonukleoliz faktlary ====
Himonukleoliz simptomatiki disk herniasy bolan näsaglaryň belli bir topary üçin hirurgiki däl bejerginiň alternatiwasy hökmünde yzygiderli garalmagyny kepillendirýär.
===== Himonukleoliziň görkezijileri =====
* Aýakda agdyklyk edýän siyatiki görnüşli agyry.
* Göni aýakda agyrynyň ýokarlanmagy synagy ýokarlandyrýar.
* 18 ýaşdan 50 ýaşa çenli näsag.
* Alamatlaryň gysga dowamlylygy.
* Surat gözleglerinde gowy lokallaşdyrylan disk herniasy.
* Surat gözlegleri alamatlar bilen baglanyşdyrylýar.
* Softumşak, uzyn disk.
* Hasaplama ýok.
* Beýleki konserwatiw bejergiler şowsuz boldy.
* Näsag operasiýadan gaça durmak isleýär.
===== Garşylyklar (himopapain ulanyp) =====
* Papaýa allergiýasy.
* Göwrelilik.
* Cauda equina sindromy ýa-da nerw ýetmezçiligi.
* Sequestered (düýbünden aýrylan) disk bölegi.
* Oňurgaly çiş.
* Oňurgara durnuksyzlygy.
* Oňurgara stenozy.
* Newrologiki kesel (meselem, köp skleroz).
* Birnäçe gezek şowsuz operasiýa.
6jqyqozmvh102dnaazi1tv8qaflmjsz
Kürtler
0
22032
268907
263881
2026-05-01T00:45:13Z
InternetArchiveBot
21623
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.5
268907
wikitext
text/x-wiki
{{Millet|ilat_sany = c. 45 million<ref name="KIOP2017">[http://www.institutkurde.org/en/info/the-kurdish-population-1232551004 The Kurdish Population] by the [[Kurdish Institute of Paris]], 2017 estimate. The Kurdish population is estimated at 15–20 million in Turkey, 10–12 million in Iran, 8–8.5 million in Iraq, 3–3.6 million in Syria, 1.2–1.5 million in the European diaspora, and 400k–500k in the former USSR—for a total of 36.4 million to 45.6 million globally.</ref><ref name="CIAonline">{{cite book |title= The World Factbook |edition= Online |date= 2015 |publisher= US [[Central Intelligence Agency]] |location= Langley, Virginia |issn= 1553-8133 |url= https://www.cia.gov/library/publications/the-world-factbook/ |access-date= 2 August 2015 |archive-date= 6 January 2019 |archive-url= https://web.archive.org/web/20190106114713/https://www.cia.gov/library/publications/the-world-factbook/ |url-status= dead }} A rough estimate in this edition gives populations of 14.3 million in Turkey, 8.2 million in Iran, about 5.6 to 7.4 million in Iraq, and less than 2 million in Syria, which adds up to approximately 28–30 million Kurds in Kurdistan or in adjacent regions. The CIA estimates are {{as of|lc=y|2015|8}} – Turkey: Kurdish 18%, of 81.6 million; Iran: Kurd 10%, of 81.82 million; Iraq: Kurdish 15–20%, of 37.01 million, Syria: Kurds, Armenians, and other 9.7%, of 17.01 million.</ref>
| etrap1 =[[File:Flag of Turkey.svg|22px]] [[Türkiýe]]
| san1 = 20,000,000
| ref1 = <ref name="CIAonline"/><ref name="KIOP2017"/>
| etrap2 =[[File:Flag of Iran.svg|22px]] [[Eýran]]
| san2 = 12,000,000
| ref2 = <ref name="CIAonline"/><ref name="KIOP2017"/>
| etrap3 =[[File:Flag of Iraq.svg|22px]] [[Yrak]]
| san3 = 8,500,000
| ref3 = <ref name="CIAonline"/><ref name="KIOP2017"/>
| etrap4 =[[File:Flag of Syria.svg|22px]] [[Siriýa]]
| san4 = 3,600,000
| ref4 = <ref name="CIAonline"/><ref name="KIOP2017"/>
| etrap5 =[[File:Flag of Afghanistan.svg|22px]] [[Owganystan]]
| san5 = 200,000
| ref5 = <ref>Cigerxwin, Tarixa Kurdistan, I (Stockholm: Weşanên Roja Nû, 1985), p. 17.</ref><ref name="auto33">{{Cite web |title=The Kurdish Diaspora |url=https://www.institutkurde.org/en/kurdorama/ |website=Institutkurde.org}}</ref>
| etrap6 =[[File:Flag of Azerbaijan.svg|22px]] [[Azarbeýjan]]
| san6 = 180,000
| ref6 = <ref name="coucileub">''[http://assembly.coe.int/Main.asp?link=/Documents/WorkingDocs/Doc06/EDOC11006.htm The cultural situation of the Kurds],'' <cite>A report by Lord Russell-Johnston, Council of Europe, July 2006</cite>.</ref><!-- تکمیل یادکرد توسط ربات از ویکی انگلیسی --><ref name="Vanly">Ismet Chériff Vanly, “The Kurds in the Soviet Union”, in: Philip G. Kreyenbroek & S. Sperl (eds.), The Kurds: A Contemporary Overview (London: Routledge, 1992). pg 164: Table based on 1990 estimates: Azerbaijan (180,000), Armenia (50,000), Georgia (40,000), Kazakhstan (30,000), Kyrghizistan (20,000), Uzbekistan (10,000), Tajikistan (3,000), Turkmenistan (50,000), Siberia (35,000), Krasnodar (20,000), Other (12,000), Total 450,000</ref>
| etrap7 =[[File:Flag of Kazakhstan.svg|22px]] [[Gazagystan]]
| san7 = 47,930
| ref7 = <ref>{{cite web |title=Ethnic composition of Kazakhstan 2021 |url=http://pop-stat.mashke.org/kazakhstan-ethnic2021.htm |access-date=4 July 2022 |archive-date=21 March 2022 |archive-url=https://web.archive.org/web/20220321205438/http://pop-stat.mashke.org/kazakhstan-ethnic2021.htm |url-status=dead }}</ref>
| etrap8 = [[File:Flag of Kyrgyzstan.svg|22px]] [[Gyrgyzstan]]
| san8 = 13,200
| ref8 = <ref>{{cite web|title= Number of resident population by selected nationality |url= http://unstats.un.org/unsd/demographic/sources/census/2010_phc/Kyrgyzstan/A5-2PopulationAndHousingCensusOfTheKyrgyzRepublicOf2009.pdf |publisher=United Nations |access-date= 9 July 2012 |url-status= dead |archive-url= https://web.archive.org/web/20120710092216/http://unstats.un.org/unsd/demographic/sources/census/2010_PHC/Kyrgyzstan/A5-2PopulationAndHousingCensusOfTheKyrgyzRepublicOf2009.pdf |archive-date= 10 July 2012 }}</ref>|etrap9 = [[File:Flag of Turkmenistan.svg|22px]] [[Türkmenistan]]|san9 = 6,090|ref9 = <ref name=turkmenistan>{{cite web|script-title=ru:Итоги всеобщей переписи населения Туркменистана по национальному составу в 1995 году.|url=http://asgabat.net/turkmenistan/itogi-vseobschei-perepisi-naselenija-turkmenistana-po-nacionalnomu-sostavu-v-1995-godu.html|publisher=asgabat.net|access-date=2 December 2012|language=ru|archive-url=https://archive.today/20120709093436/http://asgabat.net/turkmenistan/itogi-vseobschei-perepisi-naselenija-turkmenistana-po-nacionalnomu-sostavu-v-1995-godu.html|archive-date=9 July 2012|url-status=dead}}</ref>| image=Flag of Kurdistan.svg|diller=[[Kürt dili|Kürtçe]]|dinler=Esasan [[Sunni Islam]], azlyk [[Shia Islam]]|footnotes=|surat_beýan=Kürdistan baýdaký}}
'''Kürtler''' ({{lang-ku|کورد|italic=yes|rtl=yes}}) ýa-da '''kürt halky''', günorta-gündogar [[Türkiýe|Türkiýäniň]], demirgazyk-günbatar [[Eýran|Eýranyň]], demirgazyk [[Yrak|Yragyň]] we [[Siriýa|Siriýanyň]] demirgazygyndaky [[Günbatar Aziýa|Günbatar Aziýanyň]] [[Kürdüstan]] daglyk sebitinde doglan [[Eýran halklary|Eýran etnik toparydyr]]. Merkezi Anadolyda, Horasanda we Kawkazda kürtleriň eksklawlary, şeýle hem günbatar Türkiýäniň (esasanam [[Stambul]] ) we [[Günbatar Ýewropa|Günbatar Europeewropanyň]] (esasanam Germaniýada ) şäherlerinde möhüm kürt diaspora jemgyýetleri bar. Kürt ilatynyň 30 bilen 45 aralygyndadygy çak edilýär milion.<ref>Based on arithmetic from ''World Factbook'' and other sources cited herein: A Near Eastern population of 28–30 million, plus approximately a 2 million diaspora gives 30–32 million. </ref>
== Häzirki zaman taryhy ==
Kürtler kürt dillerinde we Eýran dilleriniň Günbatar Eýran şahasyna degişli Zaza - Gorani dillerinde gürleýärler. [[Birinji jahan urşy]] we [[Osman imperiýasy]] ýeňilenden soň, ýeňiş gazanan Günbatar ýaranlary 1920-nji ýyldaky Söwres şertnamasynda kürt döwletini üpjün etdi. Şeýle-de bolsa, [[Lozen şertnamasy|Lozanna şertnamasy]] häzirki Türkiýäniň çägini kesgitlänsoň we üç ýyl soň bozuldy, täze ýurtlaryň hemmesinde kürtler azlyk statusyna eýe boldy.<ref>[http://www.bbc.com/news/world-middle-east-29702440 Who are the Kurds?] by ''[[BBC News]]'', 31 October 2017</ref> Kürtleriň soňky taryhynda Türk, Eýran, Siriýa we Yrak Kürdüstanda dowam edýän ýaragly çaknyşyklar bilen birlikde köp sanly genosid we gozgalaň bar. Yrakdaky we Siriýadaky kürtleriň özbaşdak sebitleri bar, kürt hereketleri bolsa tutuş Kürdüstanda has uly medeni hukuklary, özbaşdaklygy we garaşsyzlygy dowam etdirýär.
== Din we dil ==
Kürtleriň köpüsi Şafygy mekdebine uýýan sünni musulmanlar, ep-esli azlyk bolsa Hanafi mekdebine.<ref>{{Cite journal|last1=Sarigil|first1=Zeki|last2=Fazlioglu|first2=Omer|date=2014|title=Exploring the roots and dynamics of Kurdish ethno-nationalism in Turkey|url=http://yoksis.bilkent.edu.tr/pdf/files/11511.pdf|journal=Nations and Nationalism|publisher=[[Bilkent University]]|volume=20|issue=3|page=447|doi=10.1111/nana.12058|hdl=11693/26432|hdl-access=free|access-date=2022-12-18|archive-date=2022-12-18|archive-url=https://web.archive.org/web/20221218061906/http://yoksis.bilkent.edu.tr/pdf/files/11511.pdf|dead-url=yes}}</ref> Sünni yslamdan başga-da, alewizm we şaýy yslamda millionlarça kürt tarapdarlary bar.<ref>{{Cite book|last=McDowall|first=David|title=The Kurds: A Nation Denied|publisher=Minority Rights Group|year=1992|isbn=9781873194300|pages=57}}</ref>
Kürtler kürt dilinde gürleýärler. Kürt diliniň üç şiwesi bar, Demirgazyk Kürtçe, Merkezi Kürtçe, we Günorta Kürtçe.<ref name="AsatrianLinguisticClassification">G. Asatrian, Prolegomena to the Study of the Kurds, Iran and the Caucasus, Vol.13, pp. 1–58, 2009: "The classification of the Kurdish dialects is not an easy task, despite the fact that there have been numerous attempts mostly by Kurdish authors to put them into a system. However, for the time being the commonly accepted classification of the Kurdish dialects is that of the late Prof. D. N. Mackenzie, the author of fundamental works in Kurdish dialectology (see Mackenzie 1961; idem 1961–1962; idem 1963a; idem 1981), who distinguished three groups of dialects: Northern, Central, and Southern."</ref>
== Salgylanmalar ==
{{Salgylanmalar|2}}
[[Kategoriýa:Halklar]]
cf717al58ftm0kfibm9dqrzug46sd6e
Hosiýat Rustam
0
22774
268906
267435
2026-05-01T00:39:27Z
InternetArchiveBot
21623
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.5
268906
wikitext
text/x-wiki
{{Şablon:Şahsyýet|name=Hosiýat Rustam|image=Xosiyat Rustamova.jpg|image_size=250px|birth_place={{Birth date and age|1971|03|19}}|caption=Hosiýat Rustam|occupation=Şahyr we terjimeçi|language=Özbek dili|nationality=Özbek|citizenship=[[File:Flag of the Uzbek Soviet Socialist Republic (1952–1991).svg|25px]] [[Özbegistan Sowet Sosialistik Respublikasy]]
[[File:Flag of Uzbekistan.svg|25px]] [[Özbegistan|Özbegistan Respublikasy]]}}
'''Hosiýat Rustam''' ({{lang-uz|Xosiyat Rustam}}) — [[şahyr]], terjimeçi, ssenariýleriň birnäçesiniň awtory<ref>{{Cite web|title=Ozarbayjonlik deputat O‘zbekistonga kelyapti|url=https://qalampir.uz/|website=Qalampir.uz|access-date=2023-08-10|language=uz}}</ref>. Häzirki wagtda «Kitob dunyosi» gazetiniň baş redaktory.<ref>{{Cite web|title=Рустамова Хосият|url=https://arboblar.uz/ru/people/rustamova-khosiyat|website=Arboblar.uz|access-date=2023-08-10|language=ru}}</ref><ref>{{Cite web|title=Узбекская поэтесса Хосият Рустамова выпускает сборник на английском языке {{!}} «Англия»|url=http://angliya.com/publication/uzbekskaya-poetessa-hosiyat-rustamova-vypuskaet-sbornik-na-angliyskom-yazyke/|website=Англия|access-date=2023-08-10}}</ref><ref>{{Cite web|title=ANTARIPA DEV PARASHAR READS KHOSIYAT RUSTAMOVA (UZBEKISTAN) in ASSAMESE TRANSLATION BY DEBASISH PARASHAR|url=https://advaitamspeaksliterary.wordpress.com/2020/04/27/antaripareadskhosiyatrustamova/|website=Advaitam Speaks Literary|date=2020-04-27|access-date=2023-08-15|language=en|last=Advaitamspeaks}}</ref> [[2022|2022-nji]] ýyldan «The World Poet Movement» Bütindünýä şahyrlar hereketiniň Özbegistandaky utgaşdyryjysy.<ref>{{Veb manbasi|url=https://plov.press/news/kultura/uzbekskaya_poetessa_khosiyat_rustamova_stala_laureatom_britanskoy_premii/|sarlavha=Узбекская поэтесса Хосият Рустамова стала лауреатом британской премии|arxivurl=https://web.archive.org/web/20230810230435/https://plov.press/news/kultura/uzbekskaya_poetessa_khosiyat_rustamova_stala_laureatom_britanskoy_premii/|arxivsana=2023-08-10|qaralgan sana=10.08.2023}}</ref><ref>{{Cite web|title=Ўзбек шоираси Хосият Рустамова Британиянинг «Hertfordshire Press Awards» мукофоти билан тақдирланди|url=https://xs.uz/uzkr/post/ozbek-shoirasi-khosiyat-rustamova-britaniyaning-hertfordshire-press-awards-mukofoti-bilan-taqdirlandi|website=xs.uz|access-date=2023-08-10|language=uzkr|arxivsana=2023-08-10|arxivurl=https://web.archive.org/web/20230810230707/https://xs.uz/uzkr/post/ozbek-shoirasi-khosiyat-rustamova-britaniyaning-hertfordshire-press-awards-mukofoti-bilan-taqdirlandi|archive-date=2023-08-10|archive-url=https://web.archive.org/web/20230810230707/https://xs.uz/uzkr/post/ozbek-shoirasi-khosiyat-rustamova-britaniyaning-hertfordshire-press-awards-mukofoti-bilan-taqdirlandi|url-status=dead}}</ref><ref>{{Cite web|title=Dunyo|url=https://dunyo.info/ru/site/inner/uzbekskaya_poetessa_hosiyat_rustamova_stala_laureatom_britanskoy_premii_hertfordshire_press_awards-Jes|website=dunyo.info|access-date=2023-08-10|archive-date=2023-08-10|archive-url=https://web.archive.org/web/20230810230435/https://dunyo.info/ru/site/inner/uzbekskaya_poetessa_hosiyat_rustamova_stala_laureatom_britanskoy_premii_hertfordshire_press_awards-Jes|url-status=dead}}</ref><ref>{{Cite web|title=Ўзбек шоираси Хосият Рустамованинг китоби Лондонда чоп қилинди|url=https://eltuz.com/asosiy-mavzular/214629/|website=El tuz|date=2020-08-08|access-date=2023-08-10|language=uz-UZ|archive-date=2020-09-25|archive-url=https://web.archive.org/web/20200925200154/https://eltuz.com/asosiy-mavzular/214629/|dead-url=yes}}</ref><ref>{{Citation|title=Ўзбек шоирасининг китоби Лондонда нашр этилди|url=https://www.youtube.com/watch?v=QCAkOvBR7rI|access-date=2023-08-15|language=en}}</ref><ref>{{Cite web|title=Acordarea premiilor literare de către Editura Hertfordshire Press la Londra|url=https://clusiumnews.ro/acordarea-premiilor-literare-de-catre-editura-hertfordshire-press-la-londra/|website=Clusium News|date=2022-11-27|access-date=2023-08-16|language=ro-RO|first=Tene|last=Ioan}}</ref>
== Terjimehal ==
[[Namangan|Namangan welaýatynyň]] Çust etrabynyň Olmos obasynda doglan. [[1993|1993-nji]] ýylda Daşkent Döwlet uniwersitetiniň (häzirki Özbegistanyň Milli uniwersiteti) žurnalistika fakultetini tamamlady.<ref>{{Cite web|title=Xosiyat Rustamova. Yomg’ir yog’ayotgan edi…She’rlar va badialar. {{!}} Xurshid Davron kutubxonasi|url=https://kh-davron.uz/yangiliklar/muborak-kin/xosiyat-rustamova-yomgir-yogayotgan-edisherlar-va-badialar.html|website=kh-davron.uz|access-date=2023-08-10}}</ref><ref>{{Cite web|title=Хосият Рустамова — Интерпоэзия|url=https://interpoezia.org/authors/xosiyat-rustamova/|website=interpoezia.org|access-date=2023-08-10}}</ref><ref>{{Cite web|title=Хосият Рустамова-одна из ярчайших поэтесс Узбекистана-презентует свой новый поэтический сборник в сердце Англии - Kultura.uz|url=https://www.kultura.uz/view_9_r_15500.html|website=www.kultura.uz|access-date=2023-08-10}}</ref><ref>{{Cite web|title=******OUR POETRY ARCHIVE******: KHOSIYAT RUSTAM|url=https://ourpoetryarchive.blogspot.com/2021/04/khosiyat-rustam.html|website=******OUR POETRY ARCHIVE******|date=2021-04-01|access-date=2023-08-22|first=Our Poetry|last=Archive}}</ref>
[[2003|2003-nji]] ýylda Özbegistanyň Milli uniwersitetiniň Özbek filologiýasy fakultetiniň ýanyndaky Ýokary Edebiýat kursuny tamamlady.
[[2021|2021-nji]] ýylda Aýowa ([[ABŞ]]) uniwersitetiniň Halkara ýazyjylyk maksatnamasyny tamamlady.<ref>{{Cite web|title=Ўзбек шоираси Озарбайжон Ёзувчилар бирлиги аъзоси бўлди|url=https://uza.uz/uz/posts/zbek-shoirasi-ozarbayzhon-yezuvchilar-birligi-azosi-b-ldi-07-11-2019|website=Uza.uz|date=2019-11-07|access-date=2023-08-15|language=uz|last=uza.uz}}</ref><ref>{{Cite web|title=Ўзбек шоираси Озарбайжоннинг юксак мукофоти билан тақдирланди|url=https://uza.uz/uz/posts/ozbek-shoirasi-ozarbayzhonning-yuksak-mukofoti-bilan-taqdirlandi_271695|website=Uza.uz|date=2021-05-31|access-date=2023-08-15|language=uz|last=uza.uz}}</ref><ref>{{Cite web|title=Xasiyyət Rüstəm Azərbaycan Yazıçılar Birliyinin fəxri üzvü seçilib|url=https://olaylar.az/news/sosial/357334|website=Olaylar.az|date=2019-11-07|access-date=2023-08-15|language=az}}</ref>
==Sylaglary==
Hosiýat Rustam [[2004|2004-nji ýylda]] [[Özbegistan|Özbegistan Respublikasynyň]] Prezidentiniň permanyna laýyklykda «Şöhrat» medaly, [[2016|2016-njy]] ýylda «Özbegistanyň Garaşsyzlygyna 25 ýyl» ýadygärlik nyşany bilen sylaglanyldy.
Halkara şygyr festiwallarynyň birnäçesine gatnaşdy. [[2012|2012-nji]] ýylda Türkiýäniň Bursa şäherinde şygyrýet boýunça Halkara bäsleşiginiň ýeňijisi boldy.r<ref>{{Cite web|title=Ўзбек шоираси Озарбайжон Ёзувчилар бирлиги аъзоси бўлди|url=https://uza.uz/uz/posts/zbek-shoirasi-ozarbayzhon-yezuvchilar-birligi-azosi-b-ldi-07-11-2019|website=Uza.uz|date=2019-11-07|access-date=2023-08-15|language=uz|last=uza.uz}}</ref><ref>{{Cite web|title=Ўзбек шоираси Озарбайжоннинг юксак мукофоти билан тақдирланди|url=https://uza.uz/uz/posts/ozbek-shoirasi-ozarbayzhonning-yuksak-mukofoti-bilan-taqdirlandi_271695|website=Uza.uz|date=2021-05-31|access-date=2023-08-15|language=uz|last=uza.uz}}</ref><ref>{{Cite web|title=Xasiyyət Rüstəm Azərbaycan Yazıçılar Birliyinin fəxri üzvü seçilib|url=https://olaylar.az/news/sosial/357334|website=Olaylar.az|date=2019-11-07|access-date=2023-08-15|language=az}}</ref>
[[2015|2015-nji]] ýylda Azerbaýjanyň Mikoil Muşfik adyndaky Milli hem halkara baýragy, 2020-nji ýylda — Nurangiz Gun adyndaky halkara şygryýet baýragy,
[[2021|2021-nji]] ýylda — Rasul Rizo adyndaky halkara baýrak bilen sylaglandy.
2017-nji ýylda Nyzamy Genjewi adyndaky edebiýat institutynyň «Azerbaýjan bilen Özbegistanyň arasynda ylmy we edebi işleri üçin» diýen baýragyna mynasyp boldy.
[[2018|2018-nji]] ýylda Tailandda geçen Halkara edebiýat festiwalynyň şygyrýet ugry boýunça ýokary baýragyna eýe boldy.
2003-nji ýyldan bäri Özbegistanyň Ýazyjylar birleşiginiň agzasy.
2019-njy ýyldan bäri Azerbaýjanyň Ýazyjylar birleşiginiň agzasy.
2021-nji ýylda Naji Naaman adyndaky halkara baýraga mynasyp boldy.
2021-nji ýylda Rossiýanyň Ýazyjylar birleşiginiň abraýly «Altyn rysar» baýragy bilen sylaglandy.
2022-nji ýylda Liwanda «Naji Naaman adyndaky Medeniýet gaznasy» Halkara guramasy (FGC) Hosiýat Rustamy «Adatdan daşary medeniýet ilçisi» diýip yglan etdi.
== Işleri ==
Şeýle-de Hosiýat Rustam ýazyjy, dramaturgdyr, ençeme hekaýalaryň, pýesalaryň, kinossenariýleriň awtory. 2015-nji ýylda onuň «Asmana sygmadyk guş» atly pýesasy Özbegistanyň esasy teatrlaryndan biri bolan Mukimiý adyndaky sazly drama teatrynda Özbegistanyň Halk artisti Mirza Azizowyň režissýorlygynda sahnalaşdyryldy.<ref>{{veb manbasi|title=Acordarea premiilor literare de către Editura Hertfordshire Press la Londra|url=https://www.diasporamadrid.com/acordarea-premiilor-literare-de-catre-editura-hertfordshire-press-la-londra/#.Y4N1pfgcroA.whatsapp|website=diasporamadrid.com|accessdate=29-dekabr 2023-yil}}</ref>
2016-njy ýylda Hosiýat Rustam «Haly» atly hekaýasy esasynda «Ajal jadysy» diýen kinossenarisini ýazdy we ol tanymal özbek režissiýory Hilol Nasimow tarapyndan ekranlaşdyryldy.
Şeýle-de ol Marina Swetaýewanyň, Ýewgeniý Ýewtuşenkonyň, Anna Ahmatowanyň, Boris Pasternagyň, Ramiz Rawşanyň, Nigor Refibeýliniň, Rizo Haliliň, Fazil Kisaküregiň, Hüsni Daglarjanyň we dünýäniň beýleki ençeme şahyrlarynyň şygyrlaryny özbek diline terjime etdi. [[2015|2015-nji]] ýyldan bäri ol ýurduň «Kitob dunyosi» atly abraýly gazetiniň baş redaktory.
Hosiýat Rustamyň eserleri dünýäniň 30-dan gowrak diline terjime edilen. Onuň kitaplary daşary ýurtlaryň ençemesinde çap edildi.
Tutuş dünýäde tanalýan ýazyjy-şahyrlaryň makalalarydyr esseleriniň esasynda döredilen «Hosiýat» atly kitap Özbegistanda özbek dilinde, Londonda («EGG London» neşirýatynda) bolsa iňlis dilinde neşire taýýarlandy.
Azerbaýjanyň we Özbegistanyň telewideniýesiniň bilelikdäki işi bolan, Hosiýat Rustam baradaky iki bölümden ybarat filmiň surata düşürilmegi dowam edýär.<ref>{{Cite web|title=Таниқли шоира ҳақида ҳужжатли фильм олинмоқда|url=https://www.namhaqiqat.uz/madaniyat/tanili_shoira_haida_huzhzhatli_film_olinmoda.html|website=namhaqiqat.uz|access-date=2023-08-10|last=namhaqiqat.uz|archive-date=2023-08-10|archive-url=https://web.archive.org/web/20230810230651/https://www.namhaqiqat.uz/madaniyat/tanili_shoira_haida_huzhzhatli_film_olinmoda.html|dead-url=yes}}</ref><ref>{{Cite web|title=Khosiyat Rustam writer from Uzbekistan invited to RomaniaVipPress-"My poems have opened the golden doors of Romania to me"!|url=https://romaniavippress.com/rustam-writer-from-uzbekistan-invited-to-romaniavippress/|website=ROMÂNIAVIPPRESS|date=2022-01-07|access-date=2023-08-22|language=ro-RO|last=romaniavippress|archive-date=2022-05-22|archive-url=https://web.archive.org/web/20220522235617/https://romaniavippress.com/rustam-writer-from-uzbekistan-invited-to-romaniavippress/|dead-url=yes}}</ref>
==Hosiýat Rustamyň Özbegistanda neşir edilen kitaplary==
# «Осмондаги уй» («Asmandaky öý») — G. Gulam adyndaky neşirýat (1997).
# «Нажот» («Ýardam») — «Manawiýat» neşirýaty (2003).
# «Ридо» («Hyrka») – «Янги аср авлоди» neşirýaty (2004).
# «Юпанч» («Teselli») – «Наманган» neşirýaty (2005).
# «Девор» («Diwar») — G. Gulam adyndaky neşirýat (2006).
# «Август» – «Янги аср авлоди» neşirýaty (2008).
# «40:0» – «Академнашр» neşirýaty (2011).
# «Ишгол» («Basyp alyş») – «Академнашр» neşirýaty (2011).
# «Унутилган йиллар» («Unudylan ýyllar») — (özbek hem türk dilinde, – «Orzu» neşirýaty (2014).
# «Бебош булутлар» («Bibaş bulutlar») – «Узбекистан» neşirýaty ( 2019).
# «Туннинг боши қоронгу» («Hamyla gije») – «Янги аср авлоди» neşirýaty (2021).
# «Саргардон соялар» («Sergezdan saýalar») – «Адабиёт» neşirýaty (2021).
# «Жигарранг дафтар» («Goňur depder») — G. Gulam adyndaky neşirýat (2021).
# «44 кун» — özbek hem azerbaýjan dilinde, «Аржуманд медиа» (2021), Daşkent.
# «Açyk penjireler» — garagalpak dilinde, «Билим», neşirýaty, (2022) Nöküs.
==Daşary ýurtlarda neşir edilen kitaplary==
# «Ertirsiz günler» – türk dilinde. «Yeni kitaplar» neşirýaty (2006), Türkiýe.
# «Gorkunç» – azerbaýjan dilinde, «Vektor» neşirýaty (2008), Azerbaýjan.
# «Söýgi anatomiýasy» – gazak dilinde, «Уш Киян» neşirýaty (2019), Gazagystan.
# «Reňbe-reň gözýaşlar» — iňlis dilinde, «EGG London» neşirýaty (2020), Beýik Britaniýa.
# «Şabbuý» – täjik dilinde, «Балоғат» neşirýaty (2020), Täjigistan.
#«Iki Güneş» — türk dilinde, «Günce Yayinlari» neşirýaty (2020), Türkiýe.
# «Şemalyň keşbi» — wýetnam dilinde, «NHA XUAT BAN HOI» neşirýaty (2020), Wýetnam.
# «Leýlisaç» — azerbaýjan dilinde, «Avropa» neşirýaty (2020), Azerbaýjan.
# «Ady Läledi ol gyzyň» — azerbaýjan dilinde, «Avropa» neşirýaty (2020), Azerbaýjan.
# «Leýlisaç» — azerbaýjan hem özbek dilinde, «Türksoy» neşirýaty (2020), Azerbaýjan.
# «Jylawlanan ýel» — belarus dilinde, «Вiшнёўка» neşirýaty (2021), Belarus.
# «Bendi edilen erkinlik» — tatar dilinde, «Доля» neşirýaty (2021), Simferopol.
# «Aýadaky ýyldyz» – ukrain dilinde, «Бураго» neşirýaty (2021), Ukraina.
# «Janyň yzy bilen» — serb dilinde, «Liberland Art» neşirýaty (2021), Serbiýa.
# «Bir owuç şemal» – türkmen dilinde, «Günce Yayinlari» neşirýaty (2021).
# «Sekiz» — rus dilinde, «Четыре» neşirýaty (2021), Россия .
# «Мен қазip очьıндамьıн» – gazak dilinde, «Аннур Сана» neşirýaty (2022), Gazagystan.
# «On üçünji aý» — ispan dilinde, «Ayame-Editotial» neşirýaty (2022), Meksika.
# «Meni ýüreklerden ide» — iňlis dilinde, «Mundus Atrium Press» neşirýaty (2023), ABŞ.
# «Reňsiz gözýaşlar» – arap dilinde, «Hassanbook» neşirýaty (2023), Saud Arabystany.
# «Goşgular hem gündelik» – rumyn dilinde, «Bistrita-Nasaud» neşirýaty (2023), Rumyniýa.
==Salgylanmalar==
{{bio-stub}}
{{Salgylanmalar|2}}
[[Kategoriýa:Şahsyýetler]]
pqm7jr30h4s347quj6xefozi0nsc7qr
MediaWiki:Gadget-Calculator
8
24652
268918
2026-05-01T10:17:43Z
Umarxon III
11129
Sahypa döretdi, mazmuny: '<sup><abbr title="{{int:gadgets-default}}">(D)</abbr></sup> {{t|calculator}} şablonyny ulanyp sahypalarda interaktiw hasaplamalary işjeňleşdirmek'
268918
wikitext
text/x-wiki
<sup><abbr title="{{int:gadgets-default}}">(D)</abbr></sup> {{t|calculator}} şablonyny ulanyp sahypalarda interaktiw hasaplamalary işjeňleşdirmek
mj862fypubba9u8euiure3ymv4w881h
MediaWiki:Gadget-calculator
8
24653
268919
2026-05-01T10:19:34Z
Umarxon III
11129
Sahypa döretdi, mazmuny: 'Ekrandaky javascript kalkulýatoryny işjeňleşdirmek'
268919
wikitext
text/x-wiki
Ekrandaky javascript kalkulýatoryny işjeňleşdirmek
hahdb98vfdh67h7s908oxdqt40o0zzg
MediaWiki:Gadget-calculator.css
8
24654
268920
2026-05-01T10:21:37Z
Umarxon III
11129
Sahypa döretdi, mazmuny: '/* Minerva has some confusing styling of input boxes */ body.skin-minerva input.calculator-field-live:not(.cdx-text-input__input):not(.cdx-checkbox__input):not(.cdx-radio__input) { background: revert; border: revert; } @media screen { /* Allow people to make fallback content */ html.client-js .calculatorgadget-enabled { display: revert !important; } html.client-js .calculatorgadget-fallback { display: none; } }'
268920
css
text/css
/* Minerva has some confusing styling of input boxes */
body.skin-minerva input.calculator-field-live:not(.cdx-text-input__input):not(.cdx-checkbox__input):not(.cdx-radio__input) {
background: revert;
border: revert;
}
@media screen {
/* Allow people to make fallback content */
html.client-js .calculatorgadget-enabled {
display: revert !important;
}
html.client-js .calculatorgadget-fallback {
display: none;
}
}
cxbfnm5qke0mfyt2o6kppiu2pvhqlan
MediaWiki:Gadget-calculator.js
8
24655
268921
2026-05-01T10:24:49Z
Umarxon III
11129
Sahypa döretdi, mazmuny: ' /******************************************************************************/ /**** THIS PAGE TRACKS [[mw:MediaWiki:Gadget-Global-Calculator.js]]. PLEASE AVOID EDITING DIRECTLY. /**** EDITS SHOULD BE PROPOSED DIRECTLY to [[mw:MediaWiki:Gadget-Global-Calculator.js]]. /**** A BOT WILL RAISE AN EDIT REQUEST IF IT BECOMES DIFFERENT FROM UPSTREAM. /******************************************************************************/ /** * On-Wiki calculator script. S...'
268921
javascript
text/javascript
/******************************************************************************/
/**** THIS PAGE TRACKS [[mw:MediaWiki:Gadget-Global-Calculator.js]]. PLEASE AVOID EDITING DIRECTLY.
/**** EDITS SHOULD BE PROPOSED DIRECTLY to [[mw:MediaWiki:Gadget-Global-Calculator.js]].
/**** A BOT WILL RAISE AN EDIT REQUEST IF IT BECOMES DIFFERENT FROM UPSTREAM.
/******************************************************************************/
/**
* On-Wiki calculator script. See [[Template:Calculator]]. Created by [[User:Bawolff]]
* License: Creative Commons Attribution-ShareAlike 3.0 and 4.0 International Public License
* Canonical source: https://mdwiki.org/wiki/MediaWiki:Gadget-calculator.js
*
* This script is designed with security in mind. Possible security risks:
* * Having a formula that executes JS
* ** To prevent this we do not use eval(). Instead we parse the formula with our own parser into an abstract tree that we evaluate by walking through it
* * Form submission & DOM clobbering - we prefix the name (and id) attribute of all fields to prevent conflicts
* * Style injection - we take the style attribute from an existing element that was sanitized by MW. We do not take style from a data attribute.
* * Client-side DoS - Formulas aren't evaluated without user interaction. Formulas have a max length. Max number of widgets per page. Ultimately, easy to revert slow formulas just like any other vandalism.
* * Prototype pollution - We use objects with null prototypes and also reject fields named __proto__ just in case.
*
* Essentially the code works by replacing certain <span> tags with <input>, parsing a custom formula language, setting up a dependency graph based on identifiers, and re-evaluating formulas on change.
*/
(function () {
var mathFuncs = [ 'abs', 'acos', 'acosh', 'asin', 'asinh', 'atan', 'atan2', 'atanh', 'cbrt', 'ceil', 'cos', 'cosh', 'exp', 'expm1', 'floor', 'hypot',
'log', 'log10', 'log2', 'log1p', 'max', 'min', 'pow', 'random', 'sign', 'sin', 'sinh', 'sqrt', 'tan', 'tanh', 'trunc', 'clz32' ];
var otherFuncs = [
'ifzero', 'coalesce', 'iffinite', 'ifnan', 'ifpositive', 'ifequal', 'round', 'jsround', 'not', 'and', 'or', 'bool', 'ifless', 'iflessorequal', 'ifgreater', 'ifgreaterorequal', 'ifbetween', 'xor',
'index', 'switch', 'radiogroup', 'bitand', 'bitor', 'bitxor', 'bitnot', 'bitleftshift', 'bitlogicrightshift', 'bitarithrightshift', 'if', 'getclick', 'timer', 'timeriterations', 'timertime'
];
var allFuncs = mathFuncs.concat(otherFuncs);
var convertFloat = function ( f ) {
if ( typeof f === 'number' ) {
return f;
}
f = f.replace( /×\s?10/, 'e' );
return parseFloat( f.replace( /[×⁰¹²³⁴⁵⁶⁷⁸⁹⁻]/g, function (m) {
return ({'⁰': 0,'¹': 1,'²': 2,'³': 3,'⁴': 4,'⁵': 5,'⁶': 6,'⁷': 7,'⁸': 8,'⁹': 9, '⁻': "-", "×": "e"})[m];
} ) );
};
// Start parser code.
var Numb = function(n) {
if ( typeof n === 'number' ) {
this.value = n;
}
this.value = parseFloat(n);
};
Numb.prototype.toString = function () { return 'Number<' + this.value + '>'; };
var Ident = function(n) {
this.value = n;
};
Ident.prototype.toString = function () { return 'IDENT<' + this.value + '>'; };
var Operator = function(val, args) {
this.op = val;
this.args = args;
};
var Null = function() { };
var Parser = function( text ) {
this.text = text;
};
var terminals = {
'IDENT': /^[a-zA-Z_][a-zA-Z0-9_]*/,
'NUMBER': /^-?[0-9]+(?:\.[0-9]+)?(?:[eE][0-9]+|×10⁻?[⁰¹²³⁴⁵⁶⁷⁸⁹]+)*/,
'WS': /^\s*/,
'PLUSMINUS': /^[+-]/,
'pi': /^(?:pi|π)(?![A-z_0-9-])/i,
'true': /^(?:true)(?![A-z_0-9-])/,
'false': /^(?:false)(?![A-z_0-9-])/,
'epsilon': /^EPSILON(?![A-z_0-9-])/,
'Infinity': /^Infinity(?![A-z_0-9-])/i,
'-Infinity': /^Infinity(?![A-z_0-9-])/i,
'NaN': /^NaN(?![A-z_0-9-])/i,
'MULTIPLYDIV': /^[*\/%×÷]/i,
};
Parser.prototype = {
check: function(id) {
if ( terminals[id] ) {
return !!(this.text.match(terminals[id]));
}
return this.text.startsWith( id );
},
consume: function(id) {
if ( terminals[id] ) {
var res = this.text.match(terminals[id]);
this.text = this.text.substring( res[0].length );
return res[0];
}
if ( this.text.startsWith( id ) ) {
this.text = this.text.substring(id.length);
return id;
}
throw new Error( "Expected " + id + " near " + this.text.substring(0,15) );
},
parse: function () {
if ( this.text === undefined || this.text === '' ) return new Null();
this.consume( 'WS' );
res = this.expression();
if( this.text.length !== 0 ) {
throw new Error( "Unexpected end of formula. Perhaps you forgot to close a parenthesis or are using an invalid function." );
}
return res;
},
expression: function () {
var text2, res, res2;
res = this.term();
this.consume( 'WS' );
while ( this.check( "PLUSMINUS" )) {
var op = this.consume( "PLUSMINUS" );
this.consume( 'WS' );
res2 = this.term();
this.consume( 'WS' );
res = new Operator( op, [ res, res2 ] );
}
return res;
},
argList: function () {
var args = [];
this.consume( 'WS' );
if ( this.check( ')' ) ) {
this.consume( ')' );
return args;
}
args[args.length] = this.expression();
this.consume( 'WS' );
while ( this.check( ',' ) ) {
this.consume( ',' );
this.consume( 'WS' );
args[args.length] = this.expression();
}
this.consume( 'WS' );
this.consume( ')' );
return args;
},
term: function () {
var text2, res, res2;
res = this.factor();
this.consume( 'WS' );
while ( this.check( "MULTIPLYDIV" )) {
var op = this.consume( "MULTIPLYDIV" );
if ( op === '×' ) op = '*';
if ( op === '÷' ) op = '/';
this.consume( 'WS' );
res2 = this.factor();
this.consume( 'WS' );
res = new Operator( op, [ res, res2 ] );
}
return res;
},
factor: function () {
this.consume( 'WS' );
var res;
if ( this.check( 'pi' ) ) {
this.consume( 'pi' );
return new Numb( Math.PI );
}
if ( this.check( 'true' ) ) {
this.consume( 'true' );
return new Numb( 1 );
}
if ( this.check( 'false' ) ) {
this.consume( 'false' );
return new Numb( 0 );
}
if ( this.check( 'Infinity' ) ) {
this.consume( 'Infinity' );
return new Numb( Infinity );
}
if ( this.check( '-Infinity' ) ) {
this.consume( '-Infinity' );
return new Numb( -Infinity );
}
if ( this.check( 'NaN' ) ) {
this.consume( 'NaN' );
return new Numb( NaN );
}
if ( this.check( 'epsilon' ) ) {
this.consume( 'epsilon' );
return new Numb( Number.EPSILON );
}
for ( var i in allFuncs ) {
if ( this.check( allFuncs[i] + '(' ) ) {
this.consume(allFuncs[i] + '(');
var argList = this.argList();
return new Operator( allFuncs[i], argList );
}
}
if ( this.check( 'IDENT' ) ) {
return new Ident( this.consume( 'IDENT' ) );
}
if ( this.check( 'NUMBER' ) ) {
return new Numb( convertFloat( this.consume( 'NUMBER' ) ) );
}
if ( this.check( '(' ) ) {
this.consume( '(' );
this.consume( 'WS' );
res = this.expression();
this.consume( 'WS' );
this.consume( ')' );
return res;
}
// unary minus
if ( this.check( '-' ) ) {
this.consume( '-' );
this.consume( 'WS' );
if ( this.check( '-' ) ) {
throw new Error( "Double unary minus without parenthesis not allowed near " + this.text.substring(0,15));
}
res = this.factor();
return new Operator( '*', [new Numb(-1), res] );
}
throw new Error( "Expected to see a term (e.g. an identifier, number, function, opening parenthesis, etc) near " + this.text.substring(0,15));
},
};
// End parser code.
// Based on https://floating-point-gui.de/errors/comparison/
var almostEquals = function( a, b ) {
var absA = Math.abs(a);
var absB = Math.abs(b);
var diff = Math.abs(a-b);
var epsilon = Number.EPSILON; /// Not sure if this is a good value for epsilon
var minNormal = Math.pow( 2, -1022 );
if ( a===b) {
return true;
}
// Min normal of double = 2^-1022
if ( a==0 || b==0 || absA+absB < minNormal ) {
return diff < epsilon * minNormal;
}
return diff / Math.min((absA + absB), Number.MAX_VALUE) < epsilon;
};
// elm: Element to get value of
// Mapping to process value with (usually this.mappingInput[element item id])
var getValueOfElm = function (elm, mapping) {
var val;
if ( elm.hasAttribute( 'data-calculator-real-value' ) ) {
// We have a non-formatted value set. Use that and skip any mappings.
return parseFloat( elm.getAttribute( 'data-calculator-real-value' ) );
} else if ( elm.tagName === 'INPUT' || elm.tagName === 'SELECT' ) {
if ( elm.type === 'radio' || elm.type === 'checkbox' ) {
// Radio/checkboxes are not allowed to have mappings
return elm.checked ? convertFloat( elm.value ) : 0;
}
val = elm.value;
} else {
val = elm.textContent;
}
if ( typeof val === 'string' ) {
val = val.trim();
var mapVals = [ val, val.toLowerCase() ];
for ( var k = 0; k < mapVals.length; k++ ) {
if (mapping && typeof mapping[mapVals[k]] === 'number' ) {
return mapping[mapVals[k]];
} else if ( mapping && [ 'Infinity', '-Infinity', 'NaN' ].indexOf( mapping[mapVals[k]] ) !== -1 ) {
return parseFloat( mapping[mapVals[k]] );
}
}
}
return convertFloat( val );
};
// Evaluate the value of an AST at runtime.
var evaluate = function( ast ) {
var that = this;
var elmList = this.elmList;
var i, then, ielse, res, elm;
if ( ast instanceof Numb ) {
return ast.value;
}
if ( ast instanceof Ident ) {
elm = elmList[ast.value];
if ( elm === undefined ) {
console.log( "Calculator: Reference to '" + ast.value + "' but there is no field by that name" );
return NaN;
}
return getValueOfElm( elm, this.mappingInput[elm.id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' )] );
}
if ( ast instanceof Operator ) {
// Special case, index() does not directly eval first arg
// index() is like an array index operator (sort of)
// It evaluates its second argument and concats that to the first identifier
if ( 'index' === ast.op ) {
if ( ast.args.length < 2 || !( ast.args[0] instanceof Ident ) ) {
return NaN;
}
var indexValue = Math.floor(this.evaluate( ast.args[1] ));
if ( !Number.isSafeInteger( indexValue ) || indexValue < 0 ) {
return NaN;
}
res = elmList[ast.args[0].value + indexValue];
if ( res === undefined ) {
return ast.args.length >= 3 ? this.evaluate( ast.args[2] ) : NaN;
}
return getValueOfElm( res, this.mappingInput[res.id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' )] );
}
// Get the value of the checked element of a radio group or NaN if none checked
// Unlike most things, first args identifies the radio group name and not an id.
if ( ast.op === 'radiogroup' ) {
if ( ast.args.length < 1 || !( ast.args[0] instanceof Ident ) ) {
return NaN;
}
var radioName = ast.args[0].value;
elm = this.parent.querySelector( 'input:checked[type=radio][name=' + CSS.escape( 'calcgadget-' + this.rand + '-' + radioName ) + ']' );
if ( !elm ) {
return NaN;
}
return this.evaluate( new Ident( elm.id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' ) ) );
}
if ( ast.op === 'getclick' ) {
if ( ast.args.length < 1 || !( ast.args[0] instanceof Ident ) ) {
return NaN;
}
switch( ast.args[0].value.toLowerCase() ) {
case 'x':
return this.clickX;
case 'prevx':
return this.clickPrevX;
case 'prevpercentx':
return this.clickPrevPercentX;
case 'startpercentx':
return this.clickStartPercentX;
case 'startx':
return this.clickStartX;
case 'percentx':
return this.clickPercentX;
case 'y':
return this.clickY;
case 'percenty':
return this.clickPercentY;
case 'prevy':
return this.clickPrevY;
case 'prevpercenty':
return this.clickPrevPercentY;
case 'starty':
return this.clickStartY;
case 'startpercenty':
return this.clickStartPercentY;
case 'inprogress':
return this.clickInProgress;
case 'type':
// 1 = start, 2 - move, 3 = done, 4 = cancel
return this.clickType;
default:
return NaN;
}
}
// Check if a specific timer is running.
// timer() returns 0 or 1 if any timer
// timer( timerName) checks for that sepcific timer
// timer(timerName, trueValue, falseValue ) returns specific value.
if ( ast.op === 'timer' ) {
if (!this.repeatInfo) {
return ast.args.length >= 3 ? this.evaluate( ast.args[2] ) : 0;
}
if (ast.args.length < 1 || (ast.args[0] instanceof Ident && ast.args[0].value === this.repeatInfo.id) ) {
return ast.args.length >= 2 ? this.evaluate( ast.args[1] ) : 1;
}
return ast.args.length >= 3 ? this.evaluate( ast.args[2] ) : 0;
}
if ( ast.op === 'timeriterations' ) {
if (!this.repeatInfo) {
return NaN;
}
if (ast.args.length < 1 || (ast.args[0] instanceof Ident && ast.args[0].value === this.repeatInfo.id) ) {
return this.repeatInfo.iterationsSoFar;
}
return NaN;
}
if ( ast.op === 'timertime' ) {
if (!this.repeatInfo) {
return NaN;
}
if (ast.args.length < 1 || (ast.args[0] instanceof Ident && ast.args[0].value === this.repeatInfo.id) ) {
return (Date.now() - this.repeatInfo.timeStart)/1000;
}
return NaN;
}
// Start of functions that evaluate their arguments.
evaledArgs = ast.args.map( function (i) { return that.evaluate( i ) } );
if ( mathFuncs.indexOf(ast.op) !== -1 ) {
return Math[ast.op].apply( Math, evaledArgs );
}
if ( 'coalesce' === ast.op ) {
for ( var k = 0; k < evaledArgs.length; k++ ) {
if ( !isNaN( evaledArgs[k] ) ) {
return evaledArgs[k];
}
}
return NaN;
}
if ( 'ifzero' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
then = evaledArgs.length < 2 ? 1 : evaledArgs[1];
ielse = evaledArgs.length < 3 ? 0 : evaledArgs[2];
return almostEquals( evaledArgs[0], 0 ) ? then : ielse;
}
// Opposite of ifzero
if ( 'if' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
then = evaledArgs.length < 2 ? 1 : evaledArgs[1];
ielse = evaledArgs.length < 3 ? 0 : evaledArgs[2];
return !almostEquals( evaledArgs[0], 0 ) ? then : ielse;
}
if ( 'ifequal' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
then = evaledArgs.length < 3 ? 1 : evaledArgs[2];
ielse = evaledArgs.length < 4 ? 0 : evaledArgs[3];
return almostEquals( evaledArgs[0], evaledArgs[1] ) ? then : ielse;
}
if ( 'iffinite' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
then = evaledArgs.length < 2 ? 1 : evaledArgs[1];
ielse = evaledArgs.length < 3 ? 0 : evaledArgs[2];
return isFinite( evaledArgs[0] ) ? then : ielse;
}
if ( 'ifnan' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
then = evaledArgs.length < 2 ? 1 : evaledArgs[1];
ielse = evaledArgs.length < 3 ? 0 : evaledArgs[2];
return isNaN( evaledArgs[0] ) ? then : ielse;
}
if ( 'ifpositive' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
then = evaledArgs.length < 2 ? 1 : evaledArgs[1];
ielse = evaledArgs.length < 3 ? 0 : evaledArgs[2];
return evaledArgs[0] >= 0 ? then : ielse;
}
// I am a bit unsure what the proper thing to do about floating point rounding issues here
// These will err on the side of returning true given rounding error. People can use ifpositive() if they
// need precise.
if ( 'ifless' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
then = evaledArgs.length < 3 ? 1 : evaledArgs[2];
ielse = evaledArgs.length < 4 ? 0 : evaledArgs[3];
return evaledArgs[0] < evaledArgs[1] && !almostEquals( evaledArgs[0], evaledArgs[1] ) ? then : ielse;
}
if ( 'ifgreater' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
then = evaledArgs.length < 3 ? 1 : evaledArgs[2];
ielse = evaledArgs.length < 4 ? 0 : evaledArgs[3];
return evaledArgs[0] > evaledArgs[1] && !almostEquals( evaledArgs[0], evaledArgs[1] ) ? then : ielse;
}
if ( 'iflessorequal' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
then = evaledArgs.length < 3 ? 1 : evaledArgs[2];
ielse = evaledArgs.length < 4 ? 0 : evaledArgs[3];
return evaledArgs[0] <= evaledArgs[1] || almostEquals( evaledArgs[0], evaledArgs[1] ) ? then : ielse;
}
if ( 'ifgreaterorequal' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
then = evaledArgs.length < 3 ? 1 : evaledArgs[2];
ielse = evaledArgs.length < 4 ? 0 : evaledArgs[3];
return evaledArgs[0] >= evaledArgs[1] || almostEquals( evaledArgs[0], evaledArgs[1] ) ? then : ielse;
}
// Should this use almostEquals???
if ( 'ifbetween' === ast.op ) {
if ( evaledArgs.length < 3 ) {
return NaN;
}
then = evaledArgs.length < 4 ? 1 : evaledArgs[3];
ielse = evaledArgs.length < 5 ? 0 : evaledArgs[4];
return evaledArgs[0] >= evaledArgs[1] && evaledArgs[0] <= evaledArgs[2] ? then : ielse;
}
if ( 'bool' === ast.op ) {
if ( evaledArgs.length !== 1 ) {
return NaN;
}
return isNaN( evaledArgs[0] ) || almostEquals( evaledArgs[0], 0.0 ) ? 0 : 1;
}
if ( 'not' === ast.op ) {
if ( evaledArgs.length !== 1 ) {
return NaN;
}
return isNaN( evaledArgs[0] ) || almostEquals( evaledArgs[0], 0.0 ) ? 1 : 0;
}
if ( 'xor' === ast.op ) {
if ( evaledArgs.length !== 2 ) {
return NaN;
}
if (
( ( isNaN( evaledArgs[0] ) || almostEquals( evaledArgs[0], 0.0 ) ) && !( isNaN( evaledArgs[1] ) || almostEquals( evaledArgs[1], 0.0 ) ) ) ||
( ( isNaN( evaledArgs[1] ) || almostEquals( evaledArgs[1], 0.0 ) ) && !( isNaN( evaledArgs[0] ) || almostEquals( evaledArgs[0], 0.0 ) ) )
) {
return 1;
} else {
return 0;
}
}
// Short circuit like in lua
if ( 'and' === ast.op ) {
for ( i = 0; i < evaledArgs.length; i++ ) {
if ( isNaN( evaledArgs[i] ) || almostEquals( evaledArgs[i], 0.0 ) ) {
return evaledArgs[i];
}
}
return evaledArgs.length >= 1 ? evaledArgs[evaledArgs.length-1] : 1;
}
if ( 'or' === ast.op ) {
for ( i = 0; i < evaledArgs.length; i++ ) {
if ( !isNaN( evaledArgs[i] ) && !almostEquals( evaledArgs[i], 0.0 ) ) {
return evaledArgs[i];
}
}
return evaledArgs.length >= 1 ? evaledArgs[evaledArgs.length-1] : 0;
}
// Bitwise operators (numbers treated like 32bit integer). The binary ones can take any number of args.
if ( 'bitand' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
res = evaledArgs[0];
for ( i = 1; i < evaledArgs.length; i++ ) {
res = res & evaledArgs[i];
}
return res;
}
if ( 'bitor' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
res = evaledArgs[0];
for ( i = 1; i < evaledArgs.length; i++ ) {
res = res | evaledArgs[i];
}
return res;
}
if ( 'bitxor' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
res = evaledArgs[0];
for ( i = 1; i < evaledArgs.length; i++ ) {
res = res ^ evaledArgs[i];
}
return res;
}
// Bitwise operators that don't take infinite amount of ops.
if ( 'bitnot' === ast.op ) {
if ( evaledArgs.length < 1 ) {
return NaN;
}
return ~evaledArgs[0];
}
if ( 'bitleftshift' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
return evaledArgs[0] << evaledArgs[1];
}
if ( 'bitlogicrightshift' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
return evaledArgs[0] >>> evaledArgs[1];
}
if ( 'bitarithrightshift' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
return evaledArgs[0] >> evaledArgs[1];
}
// switch(value,2,valueFor2,5,valueFor5,...,valueIfNoneMatch)
// select the arg where the test <= value.
if ( 'switch' === ast.op ) {
if ( evaledArgs.length < 2 ) {
return NaN;
}
var defaultVal = NaN;
if ( evaledArgs.length % 2 === 0 ) {
// Last arg is default if even number of args.
defaultVal = evaledArgs[evaledArgs.length-1];
}
for ( i = 1; i < evaledArgs.length-1; i+=2 ) {
if ( evaledArgs[0] <= evaledArgs[i] || almostEquals( evaledArgs[i], evaledArgs[0] ) ) {
return evaledArgs[i+1];
}
}
return defaultVal;
}
// js Math.round is weird. Do our own version. Based on https://stackoverflow.com/questions/11832914/how-to-round-to-at-most-2-decimal-places-if-necessary
// This does round-half away from zero, with floating point error correction.
if ( 'round' === ast.op ) {
var decimals = evaledArgs.length >= 2 ? evaledArgs[1] : 0;
var p = Math.pow( 10, decimals );
var n = (evaledArgs[0] * p) * (1 + Number.EPSILON);
return Math.round(n)/p;
}
// In case anyone wants normal js round.
if ( ast.op === 'jsround' ) {
return Math.round.apply( Math, evaledArgs );
}
if ( evaledArgs.length !== 2 ) {
throw new Error( "Unexpected number of args for " + ast.op );
}
if ( ast.op === '*' ) return evaledArgs[0]*evaledArgs[1];
if ( ast.op === '/' ) return evaledArgs[0]/evaledArgs[1];
if ( ast.op === '+' ) return evaledArgs[0]+evaledArgs[1];
if ( ast.op === '-' ) return evaledArgs[0]-evaledArgs[1];
if ( ast.op === '%' ) return evaledArgs[0]%evaledArgs[1];
throw new Error( "Unrecognized operator " + ast.op );
}
return NaN;
};
// Start dependency graph code
var getIdentifiers = function( tree ) {
if ( tree instanceof Ident ) {
return new Set([tree.value]);
}
if ( tree instanceof Operator ) {
var res = new Set([]);
var i = 0;
if ( tree.op === 'index' && tree.args.length > 0 ) {
// Special case - index allows indirect references decided at runtime
// A future version might handle this more dynamically.
i++;
getIdentifiers( tree.args[0] ).forEach( function (x) { res.add(x + '*') } );
} else if ( (tree.op === 'radiogroup' ) && tree.args.length > 0 && tree.args[0] instanceof Ident ) {
i++;
res.add( '--radio-' + tree.args[0].value );
} else if ( (tree.op === 'timer' ) ) {
if ( tree.args.length > 0 && tree.args[0] instanceof Ident ) {
i++;
res.add( '--timer-active-' + tree.args[0].value );
} else {
res.add( '--timer-active' );
}
} else if ( tree.op === 'timertime' || tree.op === 'timeriterations' ) {
res.add( '--timer-iteration' );
if ( tree.args.length > 0 && tree.args[0] instanceof Ident ) {
res.add( '--timer-iteration-' + tree.args[0].value );
} else {
res.add( '--timer-iteration' );
}
return res;
} else if ( tree.op === 'getclick' ) {
res.add( '--click' );
return res;
}
for ( ; i < tree.args.length; i++ ) {
getIdentifiers( tree.args[i] ).forEach( function (x) { res.add(x) } );
}
return res;
}
return new Set();
};
// e.g. if a=foo+1 then backlinks['foo'] = ['a',...]
var buildBacklinks = function (items, radioGroups) {
var backlinks = Object.create(null);
for ( var item in items ) {
var idents = getIdentifiers( items[item] );
// Set does not do for..in loops, and this version MW forbids for..of
idents.forEach( function (ident) {
if ( !backlinks[ident] ) {
backlinks[ident] = [];
}
backlinks[ident].push( item );
} );
}
// We use identifiers starting with -- as special purpose
// Each radio item should depend on each other, since if one gets checked the others get unchecked.
var identPrefix = '--radio-';
for ( var groupName in radioGroups ) {
if ( backlinks[identPrefix + groupName] == undefined ) {
backlinks[identPrefix + groupName] = [];
}
for ( var i = 0; i < radioGroups[groupName].length; i++ ) {
backlinks[identPrefix + groupName].push( radioGroups[groupName][i] );
if ( backlinks[radioGroups[groupName][i]] == undefined ) {
backlinks[radioGroups[groupName][i]] = [];
}
backlinks[radioGroups[groupName][i]].push( identPrefix + groupName );
}
}
return backlinks;
};
// End dependency graph code
// Start code that does setup and HTML interaction.
var setup = function ( $content ) {
// We allow users to group calculator widgets inside a <div class="calculator-container"> to scope
// ids just to that group. That way you can use a specific template multiple times on the same page.
// (Perhaps we should turn it into a <fieldset>?).
var containers = Array.from( $content.find( '.calculator-container' ) );
for ( var i = 0; i < containers.length; i++ ) {
var calc = new CalculatorWidgets( Array.from( containers[i].getElementsByClassName( 'calculator-field' ) ), containers[i] );
}
// Anything not in a container is scoped to the page.
var elms = Array.from( $content.find( '.calculator-field' ) );
new CalculatorWidgets( elms, $content[0] );
$content.find( '.calculator-field-label').replaceWith( function() {
var l = $( '<label>' );
// For accessibility reasons with codex, we sometimes have to make a label with no for attribute (e.g. radiogroups)
if ( this.dataset.for !== undefined ) {
if ( !this.dataset.for.match( /^(?:calcdisambig-\d+-)?calculator-field-[a-zA-Z_][a-zA-Z0-9_]*$/ ) || !$content.find( '#' + $.escapeSelector( this.dataset.for ) ).length ) {
return this;
}
l.attr( 'for', this.dataset.for );
}
if ( this.id ) {
l.attr( 'id', this.id );
}
if ( this.title ) {
l.attr( 'title', this.title );
}
if ( this.style.cssText ) {
l.attr( 'style', this.style.cssText );
}
if ( this.classList.contains( 'cdx-label__label' ) ) {
mw.loader.load( '@wikimedia/codex' );
}
if ( this.className !== 'calculator-field-label' ) {
var extraClass = this.dataset.calculatorClass === undefined ? '' : ' ' + this.dataset.calculatorClass;
l.attr( 'class', this.className.replace( /(^| )calculator-field-label( |$)/g, ' ' ) + extraClass );
}
l.append( this.childNodes );
return l;
} );
};
var doStats = function () {
if ( window.calculatorStatsAlreadyDone !== true ) {
window.calculatorStatsAlreadyDone = true;
mw.track( 'stats.mediawiki_gadget_calculator_total' );
mw.track( 'stats.mediawiki_gadget_calculator_wiki_total', 1, { wiki: mw.config.get( 'wgDBname' ) } );
var pageName = encodeURIComponent( mw.config.get( 'wgTitle' ).replace( / /g, '_' ) );
// alphanumeric and underscore only.
pageName = pageName.replace( /[^a-zA-Z0-9_]/g, '_' );
mw.track( 'stats.mediawiki_gadget_calculator_page_total', 1, { wiki: mw.config.get( 'wgDBname' ), page: pageName, NS: mw.config.get( 'wgNamespaceNumber' ) } );
}
};
var createSelect = function( elm ) {
var sel = document.createElement( 'select' );
var mapping;
try {
mapping = JSON.parse( elm.dataset.calculatorMapping );
} catch(e) {
console.log( "Calculator: Error processing mapping of " + elm.id + ". " + e.message );
return sel;
}
if ( typeof mapping !== 'object' ) {
console.log( "Calculator: Error processing mapping of " + elm.id + ". unexpected type" );
return sel;
}
var opt;
for ( var i in mapping ) {
if ( typeof mapping[i] === 'object' ) {
var optgroup = document.createElement( 'optgroup' );
optgroup.label = i;
sel.appendChild( optgroup );
// Note: optgroup cannot be nested.
for ( var j in mapping[i] ) {
if ( typeof mapping[i][j] === 'number' || [ 'Infinity', '-Infinity', 'NaN' ].indexOf( mapping[i][j] ) !== -1 ) {
opt = document.createElement( 'option' );
opt.value = mapping[i][j];
opt.appendChild( document.createTextNode( j ) );
optgroup.appendChild( opt );
}
}
} else if ( typeof mapping[i] === 'number' || [ 'Infinity', '-Infinity', 'NaN' ].indexOf( mapping[i] ) !== -1 ) {
opt = document.createElement( 'option' );
opt.value = mapping[i];
opt.appendChild( document.createTextNode( i ) );
sel.appendChild( opt );
}
}
return sel;
};
var CalculatorWidgets = function( elms, parent ) {
this.parent = parent;
this.rand = Math.floor(Math.random()*1000000000);
this.itemList = Object.create(null);
this.elmList = Object.create(null);
this.backlinks = Object.create(null);
this.inProgressRefresh = undefined;
this.clickX = this.clickY = this.clickPercentX = this.clickPercentY = NaN;
this.clickPrevX = this.clickPrevY = this.clickPrevPercentX = this.clickPrevPercentY = NaN;
this.clickStartX = this.clickStartY = this.clickStartPercentX = this.clickStartPercentY = NaN;
this.clickType = NaN;
this.clickInProgress = 0;
this.repeatInfo = null;
var that = this;
var radioGroups = Object.create(null);
this.buttonInfo = Object.create(null);
this.mappingInput = Object.create(null);
this.mappingOutput = Object.create(null);
this.repeatInfo = null;
if (elms.length > 200) {
console.log( "Too many calculator widgets on page" );
return;
}
for ( var i in elms ) {
var elm = elms[i];
var formula = elm.dataset.calculatorFormula;
if ( formula && formula.length > 2000 ) {
console.log( "Skipping element with too long formula" );
continue;
}
var type = elm.dataset.calculatorType;
var readonly = !!elm.dataset.calculatorReadonly;
var id = elm.id.toString();
if ( id.match( /^(?:calcdisambig-\d+-)?calculator-field-__proto__$/ ) || elm.dataset.calculatorName === '__proto__' ) {
// be paranoid
throw new Error( "Invalid calculator id: " + id );
}
if ( !id ) {
id = 'calculator-field-auto' + Math.floor( Math.random()*10000000 ) + 'unnamed';
}
if ( this.parent.classList.contains( 'calculator-container' ) && document.getElementById( id ) !== elm ) {
// There is a duplicate id, and we are not at the top level.
// This is not ideal, as you can't reference it using other attributes (e.g. aria-labelledby, aria-describedby) or link targets, but at least we won't label the wrong thing.
var labelsToUpdate = this.parent.querySelectorAll( ":scope .calculator-field-label[data-for=\"" + CSS.escape( id ) + "\"]" );
id = 'calcdisambig-' + this.rand + '-' + id;
for ( var l = 0; l < labelsToUpdate.length; l++ ) {
labelsToUpdate[l].dataset.for = id;
}
}
var defaultVal = ("" + elm.textContent).trim();
if ( type === undefined || !id.match( /^(?:calcdisambig-\d+-)?calculator-field-[a-zA-Z_][a-zA-Z0-9_]*$/ ) ) {
console.log( "Skipping " + id );
continue;
}
var formulaAST;
try {
formulaAST = (new Parser( formula )).parse();
} catch( e ) {
console.log( "Error parsing formula of " + id + ": " + e.message + ". Formula given:" + formula );
continue;
}
if ( elm.className.match( /(^| )cdx-/ ) || ( elm.dataset.calculatorClass && elm.dataset.calculatorClass.match( /(^| )cdx-/ ) ) ) {
// The input is using CSS-only codex modules. Unfortunately i think we have to load the whole thing.
// we are going to have a flash of unstyled content no matter what we do since we are converting elements at load time, so don't worry about that.
mw.loader.load( '@wikimedia/codex' );
}
if ( type !== 'plain' && type !== 'passthru' ) {
var input = type === 'select' ? createSelect( elm ) : document.createElement( 'input' );
input.className = 'calculator-field-live';
if ( elm.className !== 'calculator-field' ) input.className += ' ' + elm.className.replace( /(^| )calculator-field($| )/g, ' ' );
input.readOnly = readonly;
// If defaultVal is empty, we start the textbox value out as empty instead of NaN.
if ( defaultVal !== '' ) {
input.value = convertFloat(defaultVal);
}
input.style.cssText = elm.style.cssText; // This should be safe because elm's css was sanitized by MW
if ( elm.dataset.calculatorSize ) {
var size = parseInt( elm.dataset.calculatorSize );
input.size = size;
// Browsers are pretty inconsistent so also set as css
// Firefox shows a number selector that seems to always be 20px wide regardless of font.
// Chrome shows the selector only on hover.
input.style.width = type === 'number' ? "calc( " + size + 'ch' + ' + 20px)': size + 'ch';
}
// Add css class, but only if the gadget is enabled.
if ( elm.dataset.calculatorClass ) input.className += ' ' + elm.dataset.calculatorClass;
if ( elm.dataset.calculatorSize ) input.size = elm.dataset.calculatorSize;
if ( elm.dataset.calculatorMax ) input.max = elm.dataset.calculatorMax;
if ( elm.dataset.calculatorMin ) input.min = elm.dataset.calculatorMin;
if ( elm.dataset.calculatorPlaceholder ) input.placeholder = elm.dataset.calculatorPlaceholder;
if ( elm.dataset.calculatorStep ) input.step = elm.dataset.calculatorStep;
if ( elm.dataset.calculatorPrecision ) input.dataset.calculatorPrecision = elm.dataset.calculatorPrecision;
if ( elm.dataset.calculatorExponentialPrecision ) input.dataset.calculatorExponentialPrecision = elm.dataset.calculatorExponentialPrecision;
if ( elm.dataset.calculatorDecimals ) input.dataset.calculatorDecimals = elm.dataset.calculatorDecimals;
if ( elm.dataset.calculatorNanText ) input.dataset.calculatorNanText = elm.dataset.calculatorNanText;
if ( type === 'radio' ) {
// Name is primarily for radio groups. Prefix to prevent dom clobbering or in case it ends up in a form somehow. Add rand to make unique between scoping containers
if ( elm.dataset.calculatorName ) input.name = 'calcgadget-' + this.rand + '-' + elm.dataset.calculatorName;
if ( radioGroups[elm.dataset.calculatorName] === undefined ) {
radioGroups[elm.dataset.calculatorName] = [];
}
radioGroups[elm.dataset.calculatorName][radioGroups[elm.dataset.calculatorName].length] = id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' );
}
if ( elm.dataset.calculatorInputmode ) input.inputMode = elm.dataset.calculatorInputmode;
if ( elm.dataset.calculatorEnterkeyhint ) input.enterKeyHint = elm.dataset.calculatorEnterkeyhint;
if ( elm.getAttribute( 'aria-describedby' ) !== null ) input.setAttribute( 'aria-describedby', elm.getAttribute( 'aria-describedby' ) );
if ( elm.getAttribute( 'aria-labelledby' ) !== null ) input.setAttribute( 'aria-labelledby', elm.getAttribute( 'aria-labelledby' ) );
if ( elm.getAttribute( 'aria-label' ) !== null ) input.setAttribute( 'aria-label', elm.getAttribute( 'aria-label' ) );
if ( elm.getAttribute( 'aria-owns' ) !== null ) input.setAttribute( 'aria-owns', elm.getAttribute( 'aria-owns' ) );
if ( elm.getAttribute( 'aria-flowto' ) !== null ) input.setAttribute( 'aria-flowto', elm.getAttribute( 'aria-flowto' ) );
if ( elm.getAttribute( 'role' ) !== null ) input.setAttribute( 'role', elm.getAttribute( 'role' ) );
// To support only setting the role if gadget is enabled
if ( elm.dataset.calculatorAriaRole ) input.setAttribute( 'role', elm.dataset.calculatorAriaRole );
if ( ['true', 'false'].indexOf( elm.dataset.calculatorAriaAtomic ) !== -1 ) input.setAttribute( 'aria-atomic', elm.dataset.calculatorAriaAtomic );
if ( elm.dataset.calculatorAriaRelevant ) input.setAttribute( 'aria-relevant', elm.dataset.calculatorAriaRelevant );
if ( ['off', 'polite', 'assertive'].indexOf( elm.dataset.calculatorAriaLive ) !== -1 ) {
input.setAttribute( 'aria-live', elm.dataset.calculatorAriaLive );
} else {
// We treat invalid values as set nothing, which inherits from parent which is different than setting off. This allows user to override
// We also set a default of polite for cells with a formula
if ( formula ) {
input.setAttribute( 'aria-live', 'polite' );
}
}
if ( type === 'number' || type === 'text' || type === 'radio' || type === 'checkbox' || type === "hidden" || type === "range" ) {
input.type = type;
}
if ( type === 'select' ) {
if ( elm.dataset.calculatorValue ) {
// Allow people to override the default value for select with attribute, as the value might not be a good non-js fallback.
input.value = elm.dataset.calculatorValue;
}
}
if ( type === 'radio' || type === 'checkbox' ) {
input.onchange = this.changeHandler.bind(this); // some browsers dont fire oninput for checkboxes/radio
if ( elm.dataset.calculatorChecked === 'true' || elm.dataset.calculatorChecked === '1' ) {
input.checked = true;
} else if ( elm.dataset.calculatorChecked === 'false' || elm.dataset.calculatorChecked === '0' ) {
input.checked = false;
} else if ( !isNaN( defaultVal ) && !almostEquals( defaultVal, 0 ) ) {
// If all else fails, use the default value to decide if it is checked
input.checked = true;
}
// Allow user to set a different checked value other than 1. This will be the value when checked by human, but not neccesarily if by formula
// We use a specific data-calculator-value if set and true, otherwise we use the defaultVal (text firstChild) if true, and 1 as a last resort.
var checkValue = convertFloat( elm.dataset.calculatorValue || '' );
if ( !isNaN( checkValue ) && !almostEquals( checkValue, 0 ) ) {
input.value = checkValue;
input.dataset.calculatorValue = checkValue;
} else if ( !isNaN( defaultVal ) && !almostEquals( defaultVal, 0 ) ) {
input.value = defaultVal;
input.dataset.calculatorValue = defaultVal;
} else {
input.value = 1;
input.dataset.calculatorValue = 1;
}
}
if ( elm.dataset.calculatorMapping && type === 'text') {
this.processMapping( elm.dataset.calculatorMapping, id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' ) );
}
input.id = id;
elm.replaceWith( input );
elm = input;
input.addEventListener( 'input', this.changeHandler.bind(this) );
} else {
elm.classList.remove( 'calculator-field' );
elm.classList.add( 'calculator-field-live' );
if ( elm.dataset.calculatorClass ) elm.className += ' ' + elm.dataset.calculatorClass;
if ( ['off', 'polite', 'assertive' ].indexOf( elm.dataset.calculatorAriaLive ) !== -1 ) {
elm.setAttribute( 'aria-live', elm.dataset.calculatorAriaLive );
} else if ( formula && type !== 'passthru' && !elm.dataset.calculatorAriaLive ) {
// Announce plain outputs by default, but allow user to override. Treat invalid value as do not set, since not setting is slightly different then the value off.
elm.setAttribute( 'aria-live', 'polite' );
}
if ( ['true', 'false'].indexOf( elm.dataset.calculatorAriaAtomic ) !== -1 ) elm.setAttribute( 'aria-atomic', elm.dataset.calculatorAriaAtomic );
// Support either directly setting role or using data attribute, in case people only want to set the role in the JS case.
if ( elm.dataset.calculatorAriaRole ) elm.setAttribute( 'role', elm.dataset.calculatorAriaRole );
if ( elm.dataset.calculatorAriaRelevant ) elm.setAttribute( 'aria-relevant', elm.dataset.calculatorAriaRelevant );
if ( elm.id === '' ) {
elm.id = id;
}
if ( elm.dataset.calculatorMapping && type === 'plain') {
this.processMapping( elm.dataset.calculatorMapping, id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' ) );
}
}
var itemId = id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' );
this.itemList[itemId] = formulaAST;
this.elmList[itemId] = elm;
}
this.backlinks = buildBacklinks( this.itemList, radioGroups );
if ( this.parent.dataset.calculatorRefreshOnLoad && this.parent.dataset.calculatorRefreshOnLoad === 'true' ) {
this.inProgressRefresh = Object.create(null);
var unsetBusy = this.setBusy();
this.refresh( Object.keys( this.itemList ).filter( function (a) { return !(that.itemList[a] instanceof Null); } ) );
this.inProgressRefresh = undefined;
unsetBusy();
}
this.setupButtons();
};
// MappingText is a json blob of {"some string": number}
// Used to map between numbers <-> strings during input/output.
CalculatorWidgets.prototype.processMapping = function ( mappingText, itemId ) {
var mapping;
try {
mapping = JSON.parse( mappingText );
} catch(e) {
console.log( "Calculator: Error processing mapping of " + itemId + ". " + e.message );
return;
}
if ( typeof mapping !== 'object' ) {
console.log( "Calculator: Error processing mapping of " + itemId + ". unexpected type" );
return;
}
this.mappingInput[itemId] = mapping;
var mapOut = Object.create(null);
for (var i in mapping) {
if ( typeof mapping[i] === 'number' || [ 'default', 'Infinity', '-Infinity', 'NaN' ].indexOf( mapping[i] ) !== -1 ) {
mapOut[mapping[i]] = i;
}
}
this.mappingOutput[itemId] = mapOut;
};
CalculatorWidgets.prototype.stopRepeat = function () {
if ( this.repeatInfo ) {
if ( this.repeatInfo.cancelIdentifier ) {
// If until param is immediately true, the interval may not have been set yet.
window.clearInterval( this.repeatInfo.cancelIdentifier );
}
var id = this.repeatInfo.id;
this.repeatInfo = null;
this.inProgressRefresh = Object.create(null);
var unsetBusy = this.setBusy();
this.refresh( [ '--timer-active', '--timer-active-' + id, '--timer-iteration', '--timer-iteration-' + id ] );
this.inProgressRefresh = undefined;
unsetBusy();
}
this.repeatInfo = null;
};
CalculatorWidgets.prototype.startRepeat = function (maxIterations, id) {
if ( this.repeatInfo ) {
console.log( "Calculator: Trying to start a repeat when one is already in progress. This should not happen" );
}
this.repeatInfo = {
maxIterations: maxIterations,
iterationsSoFar: 0,
cancelIdentifier: null,
timeStart: Date.now(),
id: id
};
// Not 100% clear if we should immediately refresh or wait for after first iteration.
if ( this.inProgressRefresh ) {
console.log( "Calculator: Refresh already in progress. This probably indicates a bug" );
} else {
this.inProgressRefresh = Object.create(null);
}
var unsetBusy = this.setBusy();
this.refresh( [ '--timer-active', '--timer-active-' + id, '--timer-iteration', '--timer-iteration-' + id ] );
this.inProgressRefresh = undefined;
unsetBusy();
};
CalculatorWidgets.prototype.incrementRepeatIteration = function (until) {
this.repeatInfo.iterationsSoFar++;
if ( this.repeatInfo.iterationsSoFar > this.repeatInfo.maxIterations ) {
this.stopRepeat();
return false;
}
if ( until ) {
var untilRes = this.evaluate( until );
if ( !isNaN( untilRes ) && !almostEquals( untilRes, 0 ) ) {
this.stopRepeat();
return false;
}
}
// Update fields that depend on the iteration number
var id = this.repeatInfo.id;
if ( this.inProgressRefresh ) {
console.log( "Calculator: Refresh already in progress. This probably indicates a bug" );
} else {
this.inProgressRefresh = Object.create(null);
}
var unsetBusy = this.setBusy();
this.refresh( [ '--timer-iteration', '--timer-iteration-' + id ] );
this.inProgressRefresh = undefined;
unsetBusy();
return true;
};
var convertEventTypeToNumber = function ( type ) {
switch( type ) {
case 'pointerdown':
return 1;
case 'pointermove':
return 2;
case 'pointerup':
case 'click':
return 3;
case 'pointerleave':
return 4;
default:
throw new Error( "Unrecognized event type: " + type );
}
};
var updateClick = function(calc) {
if ( calc.inProgressRefresh ) {
console.log( "Calculator: Refresh already in progress. This probably indicates a bug" );
} else {
calc.inProgressRefresh = Object.create(null);
}
var unsetBusy = calc.setBusy();
calc.refresh( [ '--click' ] );
calc.inProgressRefresh = undefined;
unsetBusy();
};
// Handler for click events on button.
var makeButtonCallback = function ( calc, forElms, formulaASTs, repeat, maxIterations, toggle, until ) {
return function buttonCallback (event) {
if ( event.type !== 'click' ) {
if ( !event.isPrimary || ( !calc.clickInProgress && event.type !== 'pointerdown' ) ) {
return; // Skip secondary pointers.
}
}
event.preventDefault();
doStats();
var unsetBusy = calc.setBusy(); // stopping previous repeats might have side effects so set busy.
if ( toggle && calc.repeatInfo && calc.repeatInfo.id === event.currentTarget.id ) {
// If we click a toggle button a second time, all we do is stop the repeat.
calc.stopRepeat();
unsetBusy();
return;
}
if ( maxIterations !== undefined || repeat !== undefined ) {
// Only one repeating callback allowed at a time.
calc.stopRepeat();
if ( maxIterations > 1 ) {
// Start a new repeat
calc.startRepeat( maxIterations, event.currentTarget.id );
}
}
var doStuff = function(event) {
var unsetBusyInner = calc.setBusy();
var stillGoing = true;
if ( calc.repeatInfo ) {
stillGoing = calc.incrementRepeatIteration(until);
}
if ( stillGoing ) {
for ( var i = 0; i < forElms.length; i++ ) {
if ( !formulaASTs[i] || !forElms[i] ) {
console.log( "Skipping button update due to invalid formula or for attribute" );
continue;
}
var res = calc.evaluate( formulaASTs[i] );
calc.setValue( forElms[i], res );
forElms[i].dispatchEvent( new InputEvent( 'input' ) );
}
}
unsetBusyInner();
};
if ( maxIterations === undefined || maxIterations >= 1 ) {
// click coordinates can be negative or > 100% if user clicked border
if ( calc.clickInProgress ) {
calc.clickPrevX = calc.clickX;
calc.clickPrevY = calc.clickY;
calc.clickPrevPercentX = calc.clickPercentX;
calc.clickPrevPercentY = calc.clickPercentY;
}
if ( event.type === 'pointerdown' ) {
// This ensures better consistency between mouse and touchscreen.
event.currentTarget.setPointerCapture( event.pointerId );
}
if ( event.type !== 'pointerleave' ) {
// The coordinates returned for pointerleave events are inconsistent.
// Especially in firefox when a context menu is opened from right click.
calc.clickX = event.offsetX;
calc.clickY = event.offsetY;
calc.clickPercentX = 100*event.offsetX/event.currentTarget.clientWidth;
calc.clickPercentY = 100*event.offsetY/event.currentTarget.clientHeight;
calc.clickInProgress = 1;
}
calc.clickType = convertEventTypeToNumber( event.type );
if ( event.type === 'click' || event.type === 'pointerdown' ) {
calc.clickPrevX = calc.clickStartX = calc.clickX;
calc.clickPrevY = calc.clickStartY = calc.clickY;
calc.clickPrevPercentX = calc.clickStartPercentX = calc.clickPercentX;
calc.clickPrevPercentY = calc.clickStartPercentY = calc.clickPercentY;
}
updateClick(calc);
doStuff();
// We are done with the click events.
if ( calc.clickType >= 3 ) {
calc.clickX = calc.clickY = calc.clickPercentX = calc.clickPercentY = NaN;
calc.clickPrevX = calc.clickPrevY = calc.clickPrevPercentX = calc.clickPrevPercentY = NaN;
calc.clickStartX = calc.clickStartY = calc.clickStartPercentX = calc.clickStartPercentY = NaN;
calc.clickInProgress = 0;
calc.clickType = NaN;
updateClick(calc);
}
}
unsetBusy();
// repeatInfo will be null if until formula evaluates to true on first iteration
if ( calc.repeatInfo && maxIterations > 1 && repeat >= 500 ) {
calc.repeatInfo.cancelIdentifier = window.setInterval( doStuff, repeat );
}
};
};
var connectButtonToFormula = function ( calc, $button, forAttr, formulaAttr, repeatDuration, maxIterations, toggle, untilAttr, draggable ) {
// The intended use case for repeats is something like a chess board that automatically plays the next move.
// We don't want it to be used for animation or otherwise harm performance, so we limit to once per 500ms and
// only allow one repeat per container to be active at a time.
if ( repeatDuration !== undefined ) {
repeatDuration = Math.max( 500, Math.ceil(parseFloat( repeatDuration )*1000 ) );
if ( isNaN( repeatDuration ) ) {
repeatDuration = undefined;
}
}
if ( toggle === 'false' ) {
toggle = false;
}
// If neither repeatDuration or maxIterations is set then the button is normal and does not repeat but does not cancel
// if repeatDuration is set and no maxIterations, then maxIterations is infinite
// If maxIterations is 0, then this cancels the current repeat without doing anything
// If maxIterations is 1, then cancel but do normal button op (but don't repeat)
if ( maxIterations !== undefined ) {
maxIterations = parseInt( maxIterations );
} else if( repeatDuration ) {
maxIterations = Infinity;
}
if ( typeof forAttr !== 'string' || typeof formulaAttr !== 'string' ) {
// Nothing to do. Skip.
return;
}
if ( formulaAttr.length > 2000 || (untilAttr && untilAttr.length > 2000 ) ) {
console.log( "Skipping button with too long formula" );
return;
}
var until = null;
try {
until = (new Parser( untilAttr )).parse();
} catch( e ) {
console.log( "Calculator: Error parsing until formula of button " + $button[0].id + ": " + e.message );
}
var forList = forAttr.split( ';' );
var formulaList = formulaAttr.split( ';' );
var forElms = [];
var formulaASTs = [];
for ( var i = 0; i < forList.length; i++ ) {
if (formulaList[i] === undefined) {
break;
}
forElms[i] = calc.elmList[forList[i]];
try {
formulaASTs[i] = (new Parser( formulaList[i] )).parse();
} catch( e ) {
formulaASTs[i] = null;
console.log( "Calculator: Error parsing formula of button " + $button[0].id + " for " + forList[i] + ": " + e.message );
}
}
if ( draggable ) {
$button[0].addEventListener( 'pointerdown', makeButtonCallback( calc, forElms, formulaASTs, repeatDuration, maxIterations, toggle, until ) );
$button[0].addEventListener( 'pointerup', makeButtonCallback( calc, forElms, formulaASTs, repeatDuration, maxIterations, toggle, until ) );
$button[0].addEventListener( 'pointerleave', makeButtonCallback( calc, forElms, formulaASTs, repeatDuration, maxIterations, toggle, until ) );
$button[0].addEventListener( 'pointermove', makeButtonCallback( calc, forElms, formulaASTs, repeatDuration, maxIterations, toggle, until ) );
} else {
$button[0].addEventListener( 'click', makeButtonCallback( calc, forElms, formulaASTs, repeatDuration, maxIterations, toggle, until ) );
}
};
CalculatorWidgets.prototype.connectButtonDisable = function ( button, formula ) {
if ( formula === '' || formula === '0' || formula === 'false' ) {
return;
}
if ( formula.length > 2000 ) {
console.log( "Calculator: Disable formula for button " + button.id + " is too long" );
return;
}
var formulaAST;
try {
formulaAST = (new Parser( formula )).parse();
} catch( e ) {
console.log( "Error parsing disable formula of button " + button.id + ": " + e.message );
return;
}
this.buttonInfo[button.id] = {
elm: button,
formula: formulaAST,
};
// Add disabled formula's dependencies to the dependency tree.
var idents = getIdentifiers( formulaAST );
var that = this;
var item = '--button-disable-' + button.id;
idents.forEach( function (ident) {
if ( !that.backlinks[ident] ) {
that.backlinks[ident] = [];
}
that.backlinks[ident].push( item );
} );
// Now set the initial value.
var curValue = this.evaluate( formulaAST );
if ( !isNaN( curValue ) && !almostEquals( curValue, 0 ) ) {
button.disabled = true;
}
};
CalculatorWidgets.prototype.setupButtons = function() {
var calc = this;
$( this.parent ).find( '.calculator-field-button' ).replaceWith( function () {
var b = $( '<button type="button">' );
if ( this.id && this.id !== '__proto__' ) {
b.attr( 'id', this.id );
} else {
b.attr( 'id', 'calculator-button-' + Math.ceil(Math.random()*100000000000) );
}
if ( this.title ) {
b.attr( 'title', this.title );
}
if ( this.style.cssText ) {
b.attr( 'style', this.style.cssText );
}
if ( this.classList.contains( 'cdx-button' ) ) {
mw.loader.load( '@wikimedia/codex' );
}
if ( this.className !== 'calculator-field-button' ) {
var extraClass = this.dataset.calculatorClass === undefined ? '' : ' ' + this.dataset.calculatorClass;
b.attr( 'class', this.className.replace( /(^| )calculator-field-button( |$)/g, ' ' ) + extraClass );
}
if ( this.dataset.calculatorAlt ) {
b.attr( 'aria-label', this.dataset.calculatorAlt );
}
if ( this.dataset.calculatorAriaLive ) {
b.attr( 'aria-live', this.dataset.calculatorAriaLive );
}
if ( this.getAttribute( 'role' ) ) {
b.attr( 'role', this.getAttribute( 'role' ) );
}
if ( this.getAttribute( 'aria-owns' ) ) {
b.attr( 'aria-owns', this.getAttribute( 'aria-owns' ) );
}
if ( this.getAttribute( 'aria-flowto' ) ) {
b.attr( 'aria-flowto', this.getAttribute( 'aria-flowto' ) );
}
if ( this.dataset.calculatorDisabled !== undefined ) {
// TODO: Would be cool if this was a formula.
if ( this.dataset.calculatorDisabled === 'true' || this.dataset.calculatorDisabled === '1' ) {
// Simple case
b.attr( 'disabled', true );
} else {
calc.connectButtonDisable( b[0], this.dataset.calculatorDisabled );
}
}
// This will be a no-op if no formula or for specified.
connectButtonToFormula(
calc,
b,
this.dataset.calculatorFor,
this.dataset.calculatorFormula,
this.dataset.calculatorDelay,
this.dataset.calculatorMaxIterations,
this.dataset.calculatorToggle,
this.dataset.calculatorUntil
);
b.append( this.childNodes );
return b;
} );
// This is meant to allow image map type things.
$( this.parent ).find( '.calculator-field-buttonraw' ).each( function () {
if (!this.id || this.id === '__proto__') {
this.id = 'calculator-button-' + Math.ceil(Math.random()*100000000000);
}
// Make sure we do not process twice.
this.classList.remove( 'calculator-field-buttonraw' );
connectButtonToFormula(
calc,
$(this),
this.dataset.calculatorFor,
this.dataset.calculatorFormula,
this.dataset.calculatorDelay,
this.dataset.calculatorMaxIterations,
this.dataset.calculatorToggle,
this.dataset.calculatorUntil,
this.classList.contains( 'calculator-field-buttondraggable' )
);
} );
};
CalculatorWidgets.prototype.changeHandler = function(e) {
this.inProgressRefresh = Object.create(null);
var unsetBusy = this.setBusy();
doStats();
e.target.removeAttribute( 'data-calculator-real-value' );
var itemId = e.target.id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' );
var itemsToRefresh = [ this.backlinks[itemId] ];
this.inProgressRefresh[itemId] = true;
this.setValueProperties( e.target, getValueOfElm( e.target, this.mappingInput[e.target.id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' )] ) );
var staritem = itemId.replace( /[0-9]+$/, '*' );
if ( itemId.match( /[0-9]+$/ ) && this.backlinks[staritem] ) {
this.inProgressRefresh[staritem] = true;
itemsToRefresh.push( this.backlinks[staritem] );
}
this.refresh( itemsToRefresh.flat() );
this.inProgressRefresh = undefined;
unsetBusy();
};
// The subset of formatting that still returns a float-like thing.
// input type=number generally expects something that looks like a float (However if given "1.200" it will preserve the 0's)
var formatNumeric = function( n, options ) {
if ( typeof n !== "number" ) {
return n;
}
if ( !isNaN( parseInt( options.calculatorDecimals ) ) ) {
return n.toFixed( parseInt( options.calculatorDecimals ) );
}
if ( !isNaN( parseInt( options.calculatorPrecision ) ) ) {
return n.toPrecision( parseInt( options.calculatorPrecision ) );
}
if ( !isNaN( parseInt( options.calculatorExponentialPrecision ) ) ) {
return n.toExponential( parseInt( options.calculatorExponentialPrecision ) );
}
return n;
};
var format = function ( n, options, mapping ) {
var res = n.toString();
if ( typeof n !== "number" ) {
return res;
}
if ( mapping && mapping[n] ) {
return mapping[n];
}
if ( mapping && mapping['default'] !== undefined ) {
return mapping['default'];
}
if ( isNaN( n ) ) {
return options.calculatorNanText ? options.calculatorNanText : '?';
}
if ( !isNaN( parseInt( options.calculatorDecimals ) ) ) {
res = n.toFixed( parseInt( options.calculatorDecimals ) );
}
if ( !isNaN( parseInt( options.calculatorPrecision ) ) ) {
res = n.toPrecision( parseInt( options.calculatorPrecision ) );
}
if ( !isNaN( parseInt( options.calculatorExponentialPrecision ) ) ) {
res = n.toExponential( parseInt( options.calculatorExponentialPrecision ) );
}
res = res.replace( /e([+-])([0-9]+)$/, function (m, sign, exp) {
var tmp = "×10";
if ( sign === '-' ) {
tmp += '⁻';
}
tmp += exp.replace( /[0-9]/g, function (m) {
return [ '⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹' ][m];
} );
return tmp;
} );
return res;
};
CalculatorWidgets.prototype.setValuePropertiesSpecialPurpose = function( id ) {
// Maybe in future we should set CSS variables for each special purpose item. Not sure if that would be useful.
if ( id.match( /^--button-disable-.+/ ) ) {
var buttonId = id.substring(17);
if ( this.buttonInfo[buttonId] ) {
var res = this.evaluate( this.buttonInfo[buttonId].formula );
this.buttonInfo[buttonId].elm.disabled = !isNaN( res ) && !almostEquals( res, 0 );
} else {
console.log( 'Tried to refresh disable status for ' + buttonId + ' but there is no such button.' );
}
}
return;
};
// Set a data attribute, classes, etc. For ease of targeting via CSS.
CalculatorWidgets.prototype.setValueProperties = function ( elm, value ) {
var itemId = elm.id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' );
elm.dataset.calculatorFieldValue = value.toFixed(5);
if ( !itemId.match( /^auto\d\d\d\d+unnamed$/ ) ) {
this.parent.style.setProperty( '--calculator-' + itemId, value );
}
if ( !isNaN( value ) && !almostEquals( value, 0 ) ) {
elm.classList.remove( 'calculator-value-false' );
elm.classList.add( 'calculator-value-true' );
} else {
elm.classList.remove( 'calculator-value-true' );
elm.classList.add( 'calculator-value-false' );
}
};
CalculatorWidgets.prototype.refresh = function (itemIds) {
var i;
// Based on https://en.wikipedia.org/wiki/Topological_sorting#Depth-first_search
var permMarks = Object.create(null);
var tempMarks = Object.create(null);
var topList = [];
var that = this;
if ( !itemIds ) {
return;
}
var visit = function( item ) {
var i;
if ( permMarks[item] ) {
return;
}
if ( tempMarks[item] ) {
if ( !item.match( /^--radio-/ ) ) {
// Loops are expected for radio buttons.
console.log( "Loop detected in calculator. '" + item + "' may not be updated properly. (This is expected for radio buttons)" );
}
return;
}
tempMarks[item] = true;
for ( i = 0; that.backlinks[item] && i < that.backlinks[item].length; i++ ) {
visit(that.backlinks[item][i]);
}
// Special case for index()
if ( item.match( /[0-9]+$/ ) ) {
var staritem = item.replace( /[0-9]+$/, '*' );
for ( i = 0; that.backlinks[staritem] && i < that.backlinks[staritem].length; i++ ) {
visit(that.backlinks[staritem][i]);
}
}
permMarks[item] = true;
topList.push(item); // later we iterate backwards through this list.
};
for ( i = 0; i < itemIds.length; i++ ) {
if ( itemIds[i] !== undefined ) {
visit( itemIds[i] );
}
}
for ( i = topList.length - 1; i >= 0; i-- ) {
var itemId = topList[i];
if ( this.inProgressRefresh[itemId] ) {
// It is expected radio buttons loop, since they are depend on each other. If you check one, the others uncheck.
if ( this.elmList[itemId].type !== 'radio' ) {
console.log( "Loop Detected! Skipping " + itemId );
}
continue;
}
if ( itemId.match( /^--/ ) ) {
// A special purpose identifier
this.setValuePropertiesSpecialPurpose( itemId );
this.inProgressRefresh[itemId] = true;
continue;
} else if ( !this.itemList[itemId] || this.itemList[itemId] instanceof Null ) {
// This mostly should not happen but might if refresh on page load is set or with radio buttons.
var elm = this.elmList[itemId];
this.setValueProperties( elm, getValueOfElm( elm, this.mappingInput[itemId] ) );
if ( this.elmList[itemId].type !== 'radio' ) {
console.log( "Tried to refresh field " + itemId + " with no formula" );
}
continue;
}
this.inProgressRefresh[itemId] = true;
var elm = this.elmList[itemId];
// Special handling for radio buttons to prevent loops from indirect clicks.
// If someone clicked a different radio button, we want to refresh dependencies of the unclicked button that is now deselected
// but we don't want to re-evaluate its formula which might reselect it.
if ( elm.type === 'radio' && this.inProgressRefresh['--radio-' + elm.name.replace( /^calcgadget-\d+-/, '' )] ) {
this.setValueProperties( elm, getValueOfElm( elm, this.mappingInput[itemId] ) );
continue;
}
var res = this.evaluate( this.itemList[itemId] );
this.setValue( elm, res );
}
};
CalculatorWidgets.prototype.setValue = function( elm, res ) {
this.setValueProperties( elm, res );
var mappingOut = this.mappingOutput[elm.id.replace( /^(?:calcdisambig-\d+-)?calculator-field-/, '' )]
if ( elm.tagName === 'INPUT' ) {
if ( elm.type === 'range' ) {
elm.value = res;
} else if ( elm.type === 'number' ) {
// We support some formatting types but not others
// as browser still expects this to be a number.
elm.setAttribute( 'data-calculator-real-value', res );
elm.value = formatNumeric( res, elm.dataset );
} else if ( elm.type === 'radio' || elm.type === 'checkbox' ) {
elm.checked = !isNaN( res ) && !almostEquals( res, 0 );
if ( !elm.checked && elm.dataset.calculatorValue ) {
// If we are unchecking this box, set its value back to the default
// so that if a human rechecks it, the value of the control is the default
// value and not the last value of the formula.
elm.value = elm.dataset.calculatorValue;
}
} else {
elm.setAttribute( 'data-calculator-real-value', res );
elm.value = format( res, elm.dataset, mappingOut );
}
} else if ( elm.tagName === 'SELECT' ) {
elm.value = res;
} else if ( elm.dataset.calculatorType !== 'passthru' ) {
// plain type.
elm.setAttribute( 'data-calculator-real-value', res );
elm.textContent = format( res, elm.dataset, mappingOut );
}
}
// Set a calculator container to busy, which pauses some screen readers until everything is updated.
// Returns a callback to unset to busy.
CalculatorWidgets.prototype.setBusy = function () {
var curVal = this.parent.getAttribute( 'aria-busy' );
if ( curVal === 'true' ) {
return function () {};
}
this.parent.setAttribute( 'aria-busy', 'true' );
var that = this;
return function () {
that.parent.setAttribute( 'aria-busy', 'false' );
};
}
CalculatorWidgets.prototype.evaluate = evaluate;
mw.hook( 'wikipage.content' ).add( setup );
} )();
591z31w11sc33d39b1w1h5hwenwncym