Wîkîferheng
kuwiktionary
https://ku.wiktionary.org/wiki/Destp%C3%AAk
MediaWiki 1.47.0-wmf.15
case-sensitive
Medya
Taybet
Gotûbêj
Bikarhêner
Gotûbêja bikarhêner
Wîkîferheng
Gotûbêja Wîkîferhengê
Wêne
Gotûbêja wêneyî
MediaWiki
Gotûbêja MediaWiki
Şablon
Gotûbêja şablonê
Alîkarî
Gotûbêja alîkariyê
Kategorî
Gotûbêja kategoriyê
Pêvek
Gotûbêja pêvekê
Nimînok
Gotûbêja nimînokê
Portal
Gotûbêja portalê
Tewandin
Gotûbêja tewandinê
Jinûvesazî
Gotûbêja jinûvesaziyê
TimedText
TimedText talk
Modul
Gotûbêja modulê
Event
Event talk
qencî
0
3166
6334414
6315642
2026-08-17T22:43:33Z
Wikihez
7344
/* Ji wêjeya klasîk */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334414
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-qencî.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[alîkarî|Alikariya]] ku [[bê]] [[beramber]] tê kirin, [[xêr]], tiştê [[baş]] yê ku mirov bo kesekî dike yan dide kesekî [[bêyî]] ku bi xwe ti [[berdêl]]ekê jê [[wergirtin|werbigire]].
#* {{bêmînak|ku}}
# Ne [[guneh]], (li gor hin [[dîn]] û [[ol]]an) kirinên ku mirov dikare pê biçe [[biheşt]]ê û xwe ji [[dûjeh]]ê [[parastin|biparêze]].
#* {{jêder|ku|jêgirtin=Ku '''qencî''' kete qelbê te lez bike, / Xerabî ji şeytaniye, wê meke.|{{Jêgirtin/Mele Xelîlê Sêrtî/Nehc-ul Enam/1988}}}}
==== Ji wêjeya klasîk ====
* {{Jêgirtin/Ehmedê Xanî/Mem û Zîn|jêgirtin=Bi-l-cumle bikin ji bo me tehsîn<br />Bêjin ko »bi '''qencî''' hate [[tedwîn]]«<br />Xelqê ji »yuhibbuhum« digergûn}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|قەنجی}}
==== Dijmane ====
{{kol2|ku
| zerer
| xirabî
| ziyan
}}
==== Jê ====
{{kol2|ku
| qencî kirin<q:lêker>
| qencîkirin<q:navdêr>
|qencîkirî<q:rengdêr>
}}
==== Têkildar ====
{{kol2|ku
| xêr
| başî
| çakî
| rindî
}}
==== Etîmolojî ====
Ji {{paş|ku|qenc|-î}}
==== Pîrepend ====
* '' Te şûşt ava bigû, ez çi bikim '''qenciya''' bidû ''
* '' Axa riyan tev dizane, riyên '''qenci'''yên bi gel qet nizane''
* '''''Qenciya''' ne li cî, mirov dike falincî''
* '''''Qencî''' qenciyan nas dike''
* '''''Qencî''' qenciyan tîne''
* '''''qenciya''' ne li cî, bela ye ''
* '''''Qencî''', licî''
* '''''qenciya''' bike biavêje avê''
* '''''Qencî''' qenciyê dikişîne, [[xirabî]], xirabiyê dikişîne''
* '' Bi qûna gincî, venamîne ji xêr û '''qencî'''''
==== Biwêj ====
* '' Xêr û '''qencî''' vala çûn''
* '' Ne bi malê xwe, lê bi malê xelkê '''qencî'''kirin''
==== Werger ====
{{werger-ser}}
* {{Z|sq}}: {{W+|sq|mirësi|m}}
* {{Z|goh}}: {{W-|goh|guotī|m}}
* {{Z|de}}: {{W+|de|Güte|m}}, {{W+|de|Gütigkeit|m}}, {{W+|de|Herzensgüte|m}}, {{W+|de|Tugend|m}}, {{W+|de|Integrität|m}}
* {{Z|rup}}: {{W-|rup|bunãtati|m}}
* {{Z|ast}}: {{W-|ast|bondá|m}}
* {{Z|bg}}: {{W+|bg|добрина|m|sc=Cyrl}}, {{W+|bg|доброта|m|sc=Cyrl}}
* {{Z|cs}}: {{W+|cs|dobrota|m}}
* {{Z|cho}}: {{W-|cho|achukma}}
* {{Z|eo}}: {{W+|eo|boneco}}
* {{Z|fa}}: {{W+|fa|خوبی}}, {{W+|fa|نیکی}}, {{W+|fa|زیبائی}}
* {{Z|fi}}: {{W+|fi|hyvyys}}
* {{Z|fr}}: {{W+|fr|bonté|m}}
*: {{Z|fro}}: {{W-|fro|bonté|m}}
* {{Z|fur}}: {{W-|fur|bontât|m}}
* {{Z|gl}}: {{W+|gl|bondade|m}}
* {{Z|got}}: {{W-|got|𐍃𐌴𐌻𐌴𐌹|m}}, {{W-|got|𐌲𐍉𐌳𐌴𐌹|m}}
* {{Z|nl}}: {{W+|nl|goedheid|m}}
* {{Z|he}}: {{W+|he|טוֹב|n}}
* {{Z|io}}: {{W+|io|boneso}}
* {{Z|enm}}: {{W-|enm|goodnesse}}
* {{Z|en}}: {{W+|en|benefaction}}, {{W+|en|goodness}}, {{W+|en|charity}}, {{W+|en|beneficence}}, {{W+|en|dole}}, {{W+|en|favor}}, {{W+|en|favour}}, {{W+|en|kindness}}, {{W+|en|philanthropy}}, {{W+|en|virtue}}, {{W+|en|uprightness}}
* {{Z|izh}}: {{W-|izh|hyvvyys}}
* {{Z|ga}}: {{W-|ga|maitheas|m}}, {{W+|ga|maith|m}}
* {{Z|itk}}: {{W-|itk|טובזה|m|tr=tovezza}}
* {{Z|it}}: {{W+|it|bontà|m}}
* {{Z|ca}}: {{W+|ca|bondat|m}}, {{W-|ca|bonesa|m}}
* {{Z|la}}: {{W-|la|bonitās|m}}
* {{Z|lt}}: {{W-|lt|gerumas}}
* {{Z|pro}}: {{W-|pro|bontat|m}}
* {{Z|oc}}: {{W+|oc|bontat|m}}, {{W-|oc|bonesa|m}}
* {{Z|pl}}: {{W+|pl|dobro|nt}}, {{W+|pl|dobroć|m}}
* {{Z|pt}}: {{W+|pt|bondade|m}}
* {{Z|ro}}: {{W+|ro|bunătate}}
* {{Z|ru}}: {{W+|ru|доброта|m|cuda=доброта́}}
* {{Z|sco}}: {{W-|sco|guidness}}
* {{Z|es}}: {{W+|es|bondad|m}}
* {{Z|sw}}: {{W+|sw|uzuri}}
* {{Z|sv}}: {{W+|sv|godhet}}
* {{Z|tl}}: {{W-|tl|buti}}
* {{Z|ta}}: {{W+|ta|நல்லது}}
* {{Z|te}}: {{W+|te|మంచితనము}}
* {{Z|tr}}: {{W+|tr|hayır}}(isim), {{W+|tr|iyilik}}, {{W+|tr|atıfet}}, {{W+|tr|himmet}}, {{W+|tr|ihsan}}, {{W+|tr|inayet}}, {{W+|tr|kayra}}, {{W+|tr|kerem}}, {{W+|tr|kıyaklık}}, {{W+|tr|lütuf}}, {{W+|tr|nimet}}, {{W+|tr|ermişlik}}, {{W+|tr|hayrat}}, {{W+|tr|lutuf}}
* {{Z|txb}}: {{W-|txb|kärtsauñe}}
* {{Z|uga}}: {{W-|uga|𐎉𐎁}}
* {{Z|vi}}: {{W-|vi|lòng tốt}}
* {{Z|vot}}: {{W-|vot|üvüz}}
* {{Z|el}}: {{W+|el|καλοσύνη|m}}
*: Kevn: {{W-|grc|ἀγαθότης|m}}, {{W-|grc|ἀγαθοσύνη|m}}
{{werger-bin}}
phtwit95z0u87x1u9cnbwitl6hfrfpn
nerîn
0
3401
6334333
6292775
2026-08-17T14:53:43Z
Wikihez
7344
/* Navdêr */
6334333
wikitext
text/x-wiki
{{bnr-h|nêrîn}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-nerîn.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Lêker 1 ===
{{lêker|ku|c=?}}
# Dengê [[canewer]]ên [[dirinde]]. Dengê [[şêr]]î û [[heywan]]ên [[dirinde]].
#: {{mk|ku|Dihî ez li baxçê caneweran bûm, min guhê xwe da şêrî demê '''dinerî'''.}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser|Dengê canewerên dirinde|1}}
* {{Z|ar}}: {{W-|ar|صوت الأسد}}
* {{Z|en}}: {{W+|en|roar}}
{{werger-bin}}
=== Lêker 2 ===
{{lêker|ku|c=x-gh}}
# ''(mêze kirin)'' {{rastnivîs|nêrîn|beş=Lêker}}
#* {{bênimûne|ku}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{rastnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}
#* {{bênimûne|ku}}
he8tmk2bmfn3q9td9l5420pq2cb536c
pirtûk
0
3502
6334511
6314666
2026-08-18T00:26:26Z
Wikihez
7344
6334511
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|pʰɪɾ'tu:k}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-pirtûk.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Latin_dictionary.jpg|thumb|Pirtûk]]
{{navdêr|ku|z=m}}
# {{e|ku|purîzm}} [[kom|Komeke]] [[kaxez]]ên bi [[nivîs]] û / yan [[resm]] yên di nav [[berg]]ekî de hatine [[çap]]kirin.
#: {{hevmane|ku|kitêb}}
#* {{jêder-kitêb|ku|jêgirtin=Min ev '''pirtûk''' sala 1959'an li Stenbolê, li Harbiye di hucra 38'an de wextê ku ez girtî bûm, nivisîbû.|r=9|nivîskar=Musa Anter|sal=1965|sala weşandinê=1999|sernav=Birîna Reş|çap=3|weşanxane=Avesta|isbn=975-7112-65-8}}
#* {{jêder-kitêb|ku|jêgirtin=Xûndina '''pertokê'''|sal=1967|nivîskar=Ezîz Akreyî|sernav=Fêrbûna Zimanê kurdî Pi Latînî|r=32|weşanxane=Çapxana Kurdistanê}}
#* {{J:Firat Cewerî:Girtî:1986|jêgirtin=Du '''pirtûkên''' kurdî bi lawê Hemîdo re hatibûn girtin. Ji bo herdu '''pirtûkan''' qala heft sal ceze dibû.}}
#* {{jêder|ku|jêgirtin=Ez vê yekê dibêjim ji ber ku ez naxwazim ku yek bi çavekî biçûk li '''pirtûka''' min binere. Ez bi behskirina van bîranînan gelekî xemgîn dibim. Ev heye şeş sal ku hevalê min bi berxê min re çûye.|{{Jêgirtin/Antoine de Saint-Exupéry/Mîrzayê Biçûk}}}}
#* {{jêder-malper|ku|jêgirtin=Gotineke pêşiyên afrîki wiha dibêje: '''Pirtûk''' baxçek gulan e di bêrîka mirovan da. '''Pirtûk''' dost e, rêber e, heval e, zana ye.|nivîskar= ciwan ikbal |sernav= Em ê Çawa Pirtûka Bidene Hezkirin? Pirtûk Çi ye? Em Çima Pirtûka Dixwûnin |url= http://www.ozedonus.net/em-e-cawa-pirtuka-bidene-hezkirin-pirtuk-ci-ye-em-cima-pirtuka-dixw-149yy.htm |dîrok=02.01.2017}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din====
* {{ku-ar|پرتووک}}
==== Formên alternatîv ====
* {{hw|ku|pertûk|pertok}}
==== Jê ====
{{kol3|ku|cure=Jê
|pirtûkfiroş
|pirtûkfiroşxane
|pirtûkgeh
|pirtûkhez
|pirtûkhezî
|pirtûkî
|pirtûkxane
|pirtûkxanevan
|pirtûkxanevanî
}}
==== Etîmolojî ====
{{nyz|ku}}. Şiklê "pirtûk" bi Ferhenga Khurdi-Tirki ya {{w|Musa Anter}} (1967){{eref|{{Ç:ku:Musa Anter:1967|r=106}}}} belav bûye. Bêriya wî di kurmancî de tenê careke di Ferhenga {{w|Qanatê Kurdo}} de bi şiklê "pertûk"{{eref|{{Ç:ku:Qanetê Kurdo:1960|r=}}}} derbas dibe. Bide ber {{hv|ckb|پەڕتووک}}, {{hv|aii|ܦܪܬܘܟ|tr=pirtūk|w=qamîş, rûl}}, {{hv|xcl|պրտու|w=papirus}} û {{hv|ar|بُرْدِيّ|w=papirus}}.
{{çavkanî-etîm}}
==== Bide ber ====
* {{g|ku|belavok}}
* {{g|ku|defter}}
* {{g|ku|kovar}}
* {{g|ku|namilke}}
* {{g|ku|rojname}}
==== Werger ====
{{werger-bnr|kitêb}}
2480gz13db324kqsoj1kk6ng7eu1awq
balafir
0
3622
6334515
6329426
2026-08-18T00:32:39Z
Wikihez
7344
6334515
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA|balafiř}} {{r-ya req}}
=== Navdêr ===
[[Wêne:Airbus A380 blue sky.jpg|thumb|Balafirek]]
{{navdêr|ku|z=m}}
# {{f|ku|pûrizm}} [[alav|Alavên]] pê[[firîn]]ê.
#: {{hevmane|ku|teyare|firoke}}
#* {{J:Hawar:1998:2|jêgirtin=Min mizgînî lê dikir, eskerekî belçîkî gote min ko di van '''balafiran''' de carina bimbeyên siwarkirî hene{{...}}}}
#* {{J:Firat Cewerî:Kevoka Spî:1996|jêgirtin={{...}}bilêta xwe ya '''balafirê''' ji ser masê hilanî, xiste bêrîka xwe{{...}}}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|بالافڕ}}
* {{ku-kr|балафьр'}}
==== Formên alternatîv ====
* {{g|ku|balefir}}
==== Jê ====
* {{g|ku|balafirgeh}}
* {{g|ku|balafirvan}}
* {{g|ku|balafirxane}}
==== Têkildar ====
* {{g|ku|firvan}}
==== Etîmolojî ====
{{neo|ku}} ji {{qertaf|ku|bala<t:jor>|fir<crz:rehê dema niha ji [[firîn]]>|hrf=ya ku li jor difire}}.
Li gorî {{w|Celadet Alî Bedirxan}} cara ewil di 1919an de di nav kurdên Geliyê Goban de li şûna {{q|ku|teyare}} hatiye çêkirin.{{eref|{{kitêb|tr|sal=1933|nivîskar=Celadet Alî Bedirxan|amadekar=Nurî Dersîmî|sernav=Mümtaz Mütefekkir CELADET ÂLİ BEDİRHAN'ın (Türkiye Reisicumhuru Gazi Mustafa Kemal Paşa Hazretlerine) Yazdığı Açık MEKTUP|sala weşandinê=1977|r=29|jêgirtin=1919 senesinde Geliye Goban Kürtleri ilk defa teyyareyi gördükleri zaman ona isim vermekte tereddüt etmemişlerdi. Teyyareye derhal BALAFIR demişlerdi.{{...}}Adeta aêroplane'nin bir tercümesi. Şu fark ile ki havada yerine yukarıda uçan, BALA yukarı demektir, FIR ise uçmak manasında olan FIRİN mastarının maddei asliyesidir.}}}}
{{çavkanî-etîm}}
==== Bide ber ====
* {{g|ku|firok}}
* {{g|ku|helîkopter}}
* {{g|ku|balafira rewiyan}}
==== Werger ====
{{werger-ser}}
* {{Z|ab}}: {{W-|ab|аероплан}}
* {{Z|af}}: {{W+|af|vliegtuig}}
* {{Z|ak}}: {{W-|ak|adupire}}, {{W-|ak|wiemhyɛn}}
* {{Z|akz}}: {{W-|akz|piɬɬawakáyka}}
* {{Z|sq}}: {{W+|sq|aeroplan|n}}
* {{Z|ale}}: {{W-|ale|igax̂tax̂}}
* {{Z|de}}: {{W+|de|Flugzeug|nt}}
* {{Z|am}}: {{W-|am|አይሮፕላን}}
* {{Z|ami}}: {{W-|ami|hikoki}}
* {{Z|aii}}: {{W-|aii|ܛܲܝܵܣܬܵܐ|m|tr=ṭāyasta}}
* {{Z|as}}: {{W-|as|আকাশীযান}}
* {{Z|ast}}: {{W+|ast|avión|n}}
* {{Z|tay}}: {{W-|tay|hikoki'}}
* {{Z|ay}}: {{W-|ay|latajamach'iwa}}
* {{Z|az}}: {{W+|az|təyyarə}}, {{W-|az|uçaq}}
* {{Z|bm}}: {{W-|bm|sannakurun}}
* {{Z|eu}}: {{W+|eu|hegazkin}}
* {{Z|ba}}: {{W-|ba|самолёт}}
* {{Z|be}}: {{W+|be|самалёт|n}}, {{W-|be|аэрапла́н|n}}
* {{Z|bn}}: {{W+|bn|খপোত}}, {{W+|bn|উড়োজাহাজ}}, {{W+|bn|বিমান}}, {{W+|bn|আকাশযান}}
* {{Z|bcl}}: {{W-|bcl|eroplano}}
* {{Z|bol}}: {{W-|bol|jirginsama}}
* {{Z|br}}: {{W+|br|karr-nij|n}}, {{W-|br|nijerez|m}}
* {{Z|bg}}: {{W+|bg|самоле́т|n}}, {{W-|bg|авио́н|n}}, {{W+|bg|аеропла́н|n}}
* {{Z|bnn}}: {{W-|bnn|kusbabai}}
* {{Z|my}}: {{W+|my|လေယာဉ်ပျံ}}
* {{Z|ce}}: {{W-|ce|кема}}, {{W-|ce|хӏаваан кема}}
* {{Z|cs}}: {{W+|cs|letoun|n}}, {{W+|cs|letadlo|nt}}
* {{Z|chr}}: {{W-|chr|ᏥᏳ ᎦᏃᎯᎵᏙᎯ}}
* {{Z|chy}}: {{W-|chy|ame'hahtȯtse}}
* {{Z|ny}}: {{W-|ny|ndege}}
* {{Z|cic}}: {{W-|cic|piini' wakaa'}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|飛機}}
*: {{Z|dng}}: {{W-|dng|фиҗи}}
*: {{Z|gan}}: {{W-|gan|飛機|tr=fi1 ji1}}
*: {{Z|hak}}: {{W-|hak|飛行機|tr=fî-hàng-kî, pî-hàng-kî, fî-yàng-kî, pî-yàng-kî}}, {{W-|hak|飛機|tr=fî-kî}}
*: {{Z|cjy}}: {{W-|cjy|飛機|tr=fei1 ji1}}
*: {{Z|cmn}}: {{W+|cmn|飛機}}, {{W+|cmn|飛行機}}
*: {{Z|mnp}}: {{W-|mnp|飛機|tr=hí-gí}}
*: {{Z|cdo}}: {{W-|cdo|飛機|tr=hĭ-*gĭ}}, {{W-|cdo|飛船|tr=buŏi-sùng}}
*: {{Z|nan}}: {{W+|nan|飛機|tr=hui-ki}}, {{W+|nan|飛行機|tr=hui-hêng-ki, poe-hêng-ki, pe-hêng-ki, hue-lêng-ki, hui-lêng-ki, poe-lêng-ki, hoe-nn̄g-ki}}, {{W+|nan|飛船|tr=poe-chûn, pe-chûn, hui-chûn}}
*: {{Z|wuu}}: {{W-|wuu|飛機}}
*: {{Z|hsn}}: {{W-|hsn|飛機|tr=fei1 ji1}}
* {{Z|ckt}}: {{W-|ckt|риӈэнэӈ|tr=riṇėnėṇ}}
* {{Z|da}}: {{W+|da|flyvemaskine|g}}, {{W+|da|fly|nt}}
* {{Z|dwu}}: {{W-|dwu|ḏikarr}}
* {{Z|ovd}}: {{W-|ovd|flygmasin|n}}, {{W-|ovd|fliuogmasin|n}}, {{W-|ovd|flygplan|nt}}
* {{Z|id}}: {{W+|id|pesawat}}, {{W+|id|pesawat terbang}}, {{W+|id|kapal terbang}}
* {{Z|ar}}: {{W+|ar|طَائِرَة|m}}, {{W+|ar|طَيَّارَة|m}}
*: {{Z|arz}}: {{W-|arz|طيارة|m|tr=ṭayyāra}}
* {{Z|hy}}: {{W+|hy|օդանավ}}, {{W+|hy|ինքնաթիռ}}, {{W+|hy|աերոպլան}}
* {{Z|eo}}: {{W-|eo|avio}}, {{W-|eo|aviadilo}}, {{W-|eo|flugmaŝino}}
* {{Z|et}}: {{W+|et|lennuk}}
* {{Z|gur}}: {{W-|gur|alupele}}
* {{Z|fa}}: {{W+|fa|هواپیما|tr=havâpeymâ}}, {{W+|fa|طیاره|tr=tayyâre}}
* {{Z|fo}}: {{W-|fo|flogfar|nt}}
* {{Z|fi}}: {{W+|fi|lentokone}}
* {{Z|fr}}: {{W+|fr|avion|n}}, {{W+|fr|aéroplane|n}}
* {{Z|fy}}: {{W-|fy|fleantúch|nt}}, {{W-|fy|fleanmasine}}
* {{Z|ff}}:
*: Adlamî: {{W-|ff|𞤤𞤢𞥄𞤲𞤢-𞤱𞤭𞥅𞤪𞤮𞥅𞤸𞤢}}, {{W-|ff|𞤢𞤪𞤢𞤨𞤵𞤤𞤫𞤤}}, {{W-|ff|𞤢𞤱𞤭𞤴𞤮𞤲}}
*: Latînî: {{W-|ff|laana-wiirooha}}, {{W-|ff|arapulel}}, {{W-|ff|awiyon}}
* {{Z|gd}}: {{W-|gd|itealan|n}}
* {{Z|gl}}: {{W+|gl|avión|n}}, {{W+|gl|aeroplano|n}}
* {{Z|gu}}: {{W-|gu|વિમાન}}
* {{Z|ka}}: {{W+|ka|თვითმფრინავი}}, {{W-|ka|აეროპლანი}}
* {{Z|gn}}: {{W-|gn|kurusu veve}}
* {{Z|haw}}: {{W-|haw|mokulele}}, {{W-|haw|mokuea}}
* {{Z|ha}}: {{W+|ha|jirgin sama}}
* {{Z|hi}}: {{W+|hi|विमान|n}}, {{W-|hi|हवाई जहाज़|n}}, {{W-|hi|वायु-यान|n}}
* {{Z|mww}}: {{W-|mww|dav hlau}}
* {{Z|nl}}: {{W+|nl|vliegtuig|nt}}
* {{Z|he}}: {{W+|he|מָטוֹס|n|tr=matós}}, {{W+|he|אֲוִירוֹן|nt|tr=avirón}}
* {{Z|io}}: {{W+|io|aeroplano}}
* {{Z|en}}: {{W+|en|airplane}}, {{W+|en|aeroplane}}
* {{Z|izh}}: {{W-|izh|samoljotta}}, {{W-|izh|aeroplana}}
* {{Z|ia}}: {{W-|ia|avion}}, {{W-|ia|aeroplano}}
* {{Z|iu}}: {{W-|iu|ᑎᖕᒥᓲᖅ}}
* {{Z|ik}}: {{W-|ik|tiŋŋun}}
* {{Z|ga}}: {{W+|ga|eitleán|n}}
* {{Z|it}}: {{W+|it|aereo|n}}, {{W+|it|aeroplano|n}}, {{W+|it|apparecchio|n}}, {{W+|it|aeromobile|m}}
* {{Z|is}}: {{W+|is|flugvél|m}}
* {{Z|ja}}: {{W+|ja|飛行機|tr=ひこうき, hikōki}}, {{W+|ja|航空機|tr=こうくうき, kōkūki}}
* {{Z|jv}}: {{W-|jv|ꦩꦺꦴꦤ꧀ꦠꦺꦴꦂꦩꦧꦸꦂ|tr=montor mabur}}
* {{Z|kl}}: {{W-|kl|timmisartoq}}
* {{Z|xnb}}: {{W-|xnb|hikoki}}
* {{Z|kn}}: {{W+|kn|ವಿಮಾನ}}, {{W-|kn|ಏರೋಪ್ಲೇನ್}}, {{W+|kn|ಏರೋಪ್ಲೇನು}}, {{W+|kn|ಗಾಳಿತೇರು}}
* {{Z|ca}}: {{W+|ca|avió|n}}
* {{Z|ckv}}: {{W-|ckv|hikuki}}
* {{Z|qu}}: {{W-|qu|antanka}}, {{W-|qu|lata pisqu}}, {{W-|qu|maraq}}
* {{Z|ks}}: {{W-|ks|پلین}}
* {{Z|ky}}: {{W+|ky|самолёт}}, {{W+|ky|учак}}, {{W+|ky|аэроплан}}
* {{Z|kg}}: {{W-|kg|ndèke}}
* {{Z|si}}: {{W-|si|ගුවන්යානය}}, {{W-|si|අහස්යානය}}
* {{Z|cku}}: {{W-|cku|piththawaika}}
* {{Z|ko}}: {{W+|ko|비행기}}, {{W+|ko|항공기}}
* {{Z|kw}}: {{W-|kw|jynn ebron|n}}
* {{Z|mfe}}: {{W-|mfe|avyon}}
* {{Z|crs}}: {{W-|crs|avyon}}
* {{Z|crl}}: {{W-|crl|ᑳᐱᒥᐦᔮᒥᑭᐦᒡ}}, {{W-|crl|ᑳᐱᐹᒥᐦᔮᒥᑭᐦᒡ}}
* {{Z|crj}}: {{W-|crj|ᑳᐱᒥᐦᔮᒪᑲᐦᒡ}}
* {{Z|lbe}}: {{W-|lbe|самолет}}, {{W-|lbe|айрипӏалан}}
* {{Z|lkt}}: {{W-|lkt|kiŋyékhiyapi}}
* {{Z|la}}: {{W-|la|aeroplanum|nt}}
* {{Z|lv}}: {{W-|lv|lidmašīna|m}}, {{W-|lv|gaisa kuģis|m}}
* {{Z|lo}}: {{W-|lo|ອາກາດຍານ}}, {{W-|lo|ເຄື່ອງບິນ}}, {{W-|lo|ເຮືອບິນ}}, {{W+|lo|ຍົນ}}
* {{Z|ln}}: {{W-|ln|mpɛ́pɔ}}
* {{Z|lt}}: {{W+|lt|lėktuvas|n}}, {{W-|lt|aeroplanas|n}}
* {{Z|mk}}: {{W-|mk|авио́н|n}}
* {{Z|mg}}: {{W+|mg|fiaramanidina}}
* {{Z|ml}}: {{W+|ml|വിമാനം}}
* {{Z|ms}}: {{W+|ms|kapal terbang}}
* {{Z|mt}}: {{W-|mt|ajruplan|n}}
* {{Z|mnc}}: {{W-|mnc|ᡩᡝᠶᡝᡨᡠᠨ}}
* {{Z|gv}}: {{W-|gv|etlan|n}}, {{W-|gv|greie etlee|n}}
* {{Z|mns-nor}}: {{W-|mns-nor|товлыӈхап}}
* {{Z|mi}}: {{W+|mi|manurere}}, {{W-|mi|eropereina}}, {{W-|mi|aropereina}}, {{W-|mi|rererangi}}, {{W+|mi|waka rererangi}}, {{W+|mi|wakarere}}
* {{Z|mr}}: {{W-|mr|विमान}}
* Marî:
*: {{Z|mhr}}: {{W-|mhr|кÿртньыгайык}}
* {{Z|hu}}: {{W+|hu|repülőgép}}, {{W+|hu|repülő}}
* {{Z|lus}}: {{W-|lus|thlawhtheihna}}
* {{Z|mwl}}: {{W-|mwl|abion|n}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|нисэх онгоц}}, {{W+|mn|онгоц}}, {{W+|mn|нисгэл}}
*: Mongolî: {{W-|mn|ᠨᠢᠰᠬᠦ ᠣᠩᠭᠤᠴᠠ}}, {{W-|mn|ᠣᠩᠭᠤᠴᠠ}}, {{W-|mn|ᠨᠢᠰᠬᠡᠯ}}
* {{Z|mos}}: {{W-|mos|nasaar-sɩlga}}, {{W-|mos|sɩlga}}
* {{Z|nah}}: {{W+|nah|tepoztōtōtl}}, {{W-|nah|tepozpatlanqui}}
* {{Z|nap}}: {{W-|nap|arioplàno|n}}, {{W-|nap|apparècchio|n}}
* {{Z|nv}}: {{W-|nv|chidí naatʼaʼí}}
* {{Z|ne}}: {{W-|ne|विमान}}, {{W+|ne|हवाईजहाज}}, {{W-|ne|प्लेन|tr=plen}}
* {{Z|niu}}: {{W-|niu|vakalele}}
* {{Z|nrf}}: {{W-|nrf|avion|n}}
* {{Z|no}}:
*: {{Z|no}}: {{W+|no|fly|nt}}
*: {{Z|nn}}: {{W-|nn|fly|nt}}
* {{Z|oj}}: {{W-|oj|bemisemagak}}
* {{Z|oc}}: {{W+|oc|avion|n}}
* {{Z|or}}: {{W-|or|ଉଡ଼ାଜାହାଜ}}, {{W+|or|ବିମାନ}}
* {{Z|om}}: {{W-|om|xiyyaara}}
* {{Z|os}}: {{W-|os|хӕдтӕхӕг}}
* {{Z|ota}}: {{W-|ota|طیاره|tr=tayyare}}
* {{Z|ug}}: {{W-|ug|ئايروپىلان}}
* {{Z|uz}}: {{W+|uz|tayyora}}, {{W+|uz|samolyot}}, {{W+|uz|aeroplan}}
* {{Z|pwn}}: {{W-|pwn|hikuki}}
* {{Z|pi}}: {{W-|pi|ākāsayāna|nt}}, {{W-|pi|vehāsayāna|nt}}
* {{Z|ps}}: {{W+|ps|الوتکه|m|tr=alwatëka}}, {{W+|ps|طياره|m|tr=tayāra}}, {{W-|ps|هوايي جهاز}}
* {{Z|pdt}}: {{W-|pdt|Loftschepp|nt}}
* {{Z|pl}}: {{W+|pl|samolot|n}}, {{W+|pl|aeroplan|n}}
* {{Z|pt}}: {{W+|pt|avião|n}}, {{W+|pt|aeroplano|n}}
* {{Z|pa}}: {{W-|pa|ਹਵਾਈ-ਜਹਾਜ਼}}, {{W-|pa|ਜਹਾਜ਼}}
* {{Z|pyu}}: {{W-|pyu|hikuki}}
* {{Z|kk}}: {{W+|kk|ұшақ}}, {{W-|kk|самолет}}, {{W-|kk|аэроплан}}
* {{Z|rm}}: {{W-|rm|aviun|n}}, {{W-|rm|eroplan|n}}
* {{Z|ro}}: {{W+|ro|aeroplan|nt}}, {{W+|ro|avion|nt}}
* {{Z|dru}}: {{W-|dru|ngiapapalay}}
* {{Z|ru}}: {{W+|ru|самолёт|n}}, {{W+|ru|аэропла́н|n}}
* {{Z|sxr}}: {{W-|sxr|saliamiapihlihli}}
* {{Z|xsy}}: {{W-|xsy|kamahayap}}
* {{Z|szy}}: {{W-|szy|hikuki}}, {{W-|szy|babelbel}}
* {{Z|se}}: {{W-|se|girdi}}
* {{Z|smn}}: {{W-|smn|kirdemmaašin}}
* {{Z|sms}}: {{W-|sms|jarplan}}
* {{Z|sm}}: {{W-|sm|vaʻalele}}
* {{Z|sa}}: {{W+|sa|विमान}}, {{W-|sa|वायु-यान}}
* {{Z|ceb}}: {{W-|ceb|eroplano}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|авѝо̄н|n}}, {{W-|sh|зра̏копло̄в|n}}
*: Latînî: {{W+|sh|avìōn|n}}, {{W+|sh|zrȁkoplōv|n}}
* {{Z|sk}}: {{W+|sk|lietadlo|nt}}
* {{Z|sl}}: {{W+|sl|letalo|nt}}, {{W+|sl|avion|n}}
* {{Z|so}}: {{W-|so|diyaarad}}
* Sorbî:
*: {{Z|hsb}}: {{W-|hsb|lětadło|nt}}
* {{Z|ses}}: {{W-|ses|beenehii}}
* {{Z|st}}: {{W+|st|sefofane}}
* {{Z|es}}: {{W+|es|avión|n}}, {{W+|es|aeroplano|n}}
* {{Z|sw}}: {{W+|sw|ndege}}, {{W+|sw|eropleni|c9|c10}}
* {{Z|sv}}: {{W+|sv|flygplan|nt}}, {{W+|sv|flygmaskin|g}}
* {{Z|shn}}: {{W-|shn|လေႇယိၼ်ႇပျၢၼ်ႇ}}, {{W-|shn|ႁိူဝ်းမိၼ်}}
* {{Z|zdj}}: {{W-|zdj|roplani}}, {{W-|zdj|ropulani}}
* {{Z|sn}}: {{W-|sn|ndege}}
* {{Z|tg}}: {{W+|tg|самолёт}}, {{W+|tg|тайёра}}, {{W+|tg|ҳавопаймо}}, {{W-|tg|аэроплан}}
* {{Z|tl}}: {{W+|tl|eroplano}}
* {{Z|ta}}: {{W+|ta|விமானம்}}, {{W+|ta|வானவூர்தி}}
* {{Z|trv}}: {{W-|trv|asu}}, {{W-|trv|skaya}}, {{W-|trv|sapah skiya}}
* {{Z|th}}: {{W+|th|เครื่องบิน}}, {{W+|th|อากาศยาน}}, {{W+|th|เรือบิน}}
* {{Z|te}}: {{W+|te|విమానము}}
* {{Z|crh}}: {{W-|crh|tayare}}, {{W-|crh|uçaq}}
* {{Z|tt}}: {{W+|tt|очкыч}}, {{W-|tt|самолёт}}, {{W-|tt|аэроплан}}
* {{Z|ti}}: {{W-|ti|ነፋሪት}}
* {{Z|tr}}: {{W+|tr|uçak}}, {{W+|tr|tayyare}}
* {{Z|tk}}: {{W-|tk|samolýot}}, {{W-|tk|uçar}}, {{W+|tk|aeroplan}}
* {{Z|bo}}: {{W-|bo|གནམ་གྲུ}}, {{W-|bo|ལྕགས་བྱ}}
* {{Z|tkl}}: {{W-|tkl|vakalele}}
* {{Z|tpi}}: {{W+|tpi|balus}}
* {{Z|to}}: {{W-|to|vakapuna}}
* {{Z|tsu}}: {{W-|tsu|hikoki}}
* {{Z|tum}}: {{W-|tum|ndege}}
* {{Z|tvl}}: {{W-|tvl|vakalele}}
* {{Z|ur}}: {{W-|ur|طَیّارَہ|n|tr=tayyāra}}, {{W-|ur|ہَوائی جَہاز|n|tr=hawāī jahāz}}, {{W-|ur|جَہاز|n|tr=jahāz}}, {{W-|ur|ہَوا پَیما|n|tr=hawā paimā}}, {{W-|ur|وِمان|n|tr=vimān}}
* {{Z|uk}}: {{W+|uk|літа́к|n}}, {{W+|uk|аеропла́н|n}}
* {{Z|vi}}: {{W+|vi|máy bay}}, {{W+|vi|phi cơ}}
* {{Z|vo}}: {{W+|vo|flitöm}}, {{W-|vo|flitömil}}, {{W-|vo|leflitöm}}
* {{Z|vro}}: {{W-|vro|linnuk}}, {{W-|vro|aeroplaan}}
* {{Z|wls}}: {{W-|wls|vakalele}}
* {{Z|war}}: {{W-|war|eroplano}}
* {{Z|cy}}: {{W+|cy|awyren|m}}
* {{Z|km}}: {{W-|km|កប៉ាល់ហោះ}}, {{W-|km|យន្តហោះ}}, {{W-|km|សកុណាយន្ត}}
* {{Z|sah}}: {{W-|sah|самолет}}, {{W-|sah|сөмөлүөт}}, {{W-|sah|көтөр-аал}}
* {{Z|tao}}: {{W-|tao|sikoki}}
* {{Z|yi}}: {{W-|yi|אַעראָפּלאַן|n}}, {{W-|yi|עראָפּלאַן|n}}
* {{Z|yij}}: {{W-|yij|barbarrnyungu}}
* {{Z|yog}}: {{W-|yog|eroplano}}
* {{Z|yoi}}: {{W-|yoi|飛行機|tr=ひぐんき, higunki}}, {{W-|yoi|飛行機|tr=ちこーき, chikouki}}
* {{Z|yo}}: {{W-|yo|ọkọ̀ òfurufú}}
* {{Z|el}}: {{W+|el|αεροπλάνο|nt}}, {{W+|el|αεροσκάφος|nt}}
* {{Z|za}}: {{W-|za|feihgih}}
{{werger-bin}}
:{{werger çavkanî|en|airplane|beş=Ev qismê Wergerê|oldid=75545646}}
== {{ziman|zza}} ==
=== Navdêr ===
{{navdêr|zza|z=}}
# [[balafir]], [[teyare]]<ref>[http://www.kovarabir.com/wp-content/uploads/Ferheng%C3%AA-Dimil%C3%AE-%C3%BB-Kurmanc%C3%AE-B-b.pdf www.kovarabir.com]</ref>
#* {{bêmînak|zza}}
=== Çavkanî ===
{{çavkanî}}
ic6a95qqp5rym1pqn6qvbkq2mgfq8pd
vêga
0
3673
6334447
6244898
2026-08-17T23:18:38Z
Wikihez
7344
6334447
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Hoker ===
{{hoker|ku}}
# [[ca]], [[vêca]], [[îca]], [[îcar]], [[îja]]
#* {{J:Bavê Nazê:1986|jêgirtin='''Vêga''' li min zivirî û min di çavên wî de xwend{{...}}}}
# [[niha]], [[vê gavê]], [[vê demê]]
#* {{bêmînak|ku}}
# [[niha]], vî [[zeman]]î, vî [[serdem]]î
#* {{bêmînak|ku}}
# [[îcar]], [[vê]] [[car]]ê
#* {{bêmînak|ku}}
==== Formên alternatîv ====
* [[ca]]
* [[îca]]
* [[îcar]]
* [[îja]]
* [[vêca]]
==== Herwate ====
* [[nihe]]
* [[niho]]
* [[nika]]
* [[noke]]
* [[nuha]]
* [[nuho]]
* [[nûka]]
* [[nûke]]
* [[vêga]]
==== Bide ber ====
* [[berê]]
* [[paşî]]
* [[pêşî]]
==== Ji wêjeyê ====
''Qaz û werdek êdî ne li golan, dîk û mirîşk ne li hewşan, berx û karik, bizin û mî, beraz û ga ne li deşt û newal û zozanan diçêrin. Ev hemû heywanên ku berê şirîkê jiyana me bûn vêga ji ber çavê me dûr dijîn.''
(Kulturname.com, 9/2009)
==== Etîmolojî ====
* [[vê]] + [[gav]] + [[-ê]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|vorläufig}}
* {{Z|en}}: {{W+|en|just now}}
{{werger-bin}}
ciuoxobrof0fqgmehf2xb7sjhl73pxj
6334466
6334447
2026-08-17T23:41:07Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6334466
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Hoker ===
{{hoker|ku}}
# [[ca]], [[vêca]], [[îca]], [[îcar]], [[îja]]
#* {{J:Bavê Nazê:1986|jêgirtin='''Vêga''' li min zivirî û min di çavên wî de xwend{{...}}}}
# [[niha]], [[vê gavê]], [[vê demê]]
#* {{bêmînak|ku}}
# [[niha]], vî [[zeman]]î, vî [[serdem]]î
#* {{bêmînak|ku}}
# [[îcar]], [[vê]] [[car]]ê
#* {{bêmînak|ku}}
==== Ji wêjeyê ====
''Qaz û werdek êdî ne li golan, dîk û mirîşk ne li hewşan, berx û karik, bizin û mî, beraz û ga ne li deşt û newal û zozanan diçêrin. Ev hemû heywanên ku berê şirîkê jiyana me bûn vêga ji ber çavê me dûr dijîn.''
(Kulturname.com, 9/2009)
==== Formên alternatîv ====
* [[ca]]
* [[îca]]
* [[îcar]]
* [[îja]]
* [[vêca]]
==== Etîmolojî ====
* [[vê]] + [[gav]] + [[-ê]]
==== Bide ber ====
* [[berê]]
* [[paşî]]
* [[pêşî]]
==== Herwate ====
* [[nihe]]
* [[niho]]
* [[nika]]
* [[noke]]
* [[nuha]]
* [[nuho]]
* [[nûka]]
* [[nûke]]
* [[vêga]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|vorläufig}}
* {{Z|en}}: {{W+|en|just now}}
{{werger-bin}}
54cvlb9xqswkcrrdk1myg35xemzx1hq
6334488
6334466
2026-08-18T00:01:28Z
Wikihez
7344
/* Herwate */
6334488
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Hoker ===
{{hoker|ku}}
# [[ca]], [[vêca]], [[îca]], [[îcar]], [[îja]]
#* {{J:Bavê Nazê:1986|jêgirtin='''Vêga''' li min zivirî û min di çavên wî de xwend{{...}}}}
# [[niha]], [[vê gavê]], [[vê demê]]
#* {{bêmînak|ku}}
# [[niha]], vî [[zeman]]î, vî [[serdem]]î
#* {{bêmînak|ku}}
# [[îcar]], [[vê]] [[car]]ê
#* {{bêmînak|ku}}
==== Ji wêjeyê ====
''Qaz û werdek êdî ne li golan, dîk û mirîşk ne li hewşan, berx û karik, bizin û mî, beraz û ga ne li deşt û newal û zozanan diçêrin. Ev hemû heywanên ku berê şirîkê jiyana me bûn vêga ji ber çavê me dûr dijîn.''
(Kulturname.com, 9/2009)
==== Formên alternatîv ====
* [[ca]]
* [[îca]]
* [[îcar]]
* [[îja]]
* [[vêca]]
==== Etîmolojî ====
* [[vê]] + [[gav]] + [[-ê]]
==== Bide ber ====
* [[berê]]
* [[paşî]]
* [[pêşî]]
==== Hevmane ====
{{kol3|ku|
|nihe
|niho
|nika
|noke
|nuha
|nuho
|nûka
|nûke
|vêga
}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|vorläufig}}
* {{Z|en}}: {{W+|en|just now}}
{{werger-bin}}
o6n86gg3a0hr99uyslspab7089yzdtf
6334526
6334488
2026-08-18T00:41:12Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6334526
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Hoker ===
{{hoker|ku}}
# [[ca]], [[vêca]], [[îca]], [[îcar]], [[îja]]
#* {{J:Bavê Nazê:1986|jêgirtin='''Vêga''' li min zivirî û min di çavên wî de xwend{{...}}}}
# [[niha]], [[vê gavê]], [[vê demê]]
#* {{bêmînak|ku}}
# [[niha]], vî [[zeman]]î, vî [[serdem]]î
#* {{bêmînak|ku}}
# [[îcar]], [[vê]] [[car]]ê
#* {{bêmînak|ku}}
==== Ji wêjeyê ====
''Qaz û werdek êdî ne li golan, dîk û mirîşk ne li hewşan, berx û karik, bizin û mî, beraz û ga ne li deşt û newal û zozanan diçêrin. Ev hemû heywanên ku berê şirîkê jiyana me bûn vêga ji ber çavê me dûr dijîn.''
(Kulturname.com, 9/2009)
==== Formên alternatîv ====
* [[ca]]
* [[îca]]
* [[îcar]]
* [[îja]]
* [[vêca]]
==== Hevmane ====
{{kol3|ku|
|nihe
|niho
|nika
|noke
|nuha
|nuho
|nûka
|nûke
|vêga
}}
==== Etîmolojî ====
* [[vê]] + [[gav]] + [[-ê]]
==== Bide ber ====
* [[berê]]
* [[paşî]]
* [[pêşî]]
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|vorläufig}}
* {{Z|en}}: {{W+|en|just now}}
{{werger-bin}}
emyolu91xfojfzy58zcdfcdd466lnbo
bikarhêner
0
3869
6334380
6300370
2026-08-17T19:03:11Z
Wikihez
7344
/* Etîmolojî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334380
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:User icon 2.svg|thumb|Îkona '''bikarhêneran''']]
{{navdêr|ku|z=mn}}
# Kesa/ê tiştekê/î [[bi kar anîn|bi kar tîne]], kesa/ê kar bi tiştekê/î dike.
#* {{jêder-malper|ku|jêgirtin= Microsoftê nûçeya zêdekirina zimanê kurdî li lîsteya xwe ya zimanên wergerê, li ser malpera xwe belav kir. Li gor nûkirina herî dawî ya Microsoftê, zaravên kurmancî û soranî, di appa wergerê ya wê kompaniyê de tên danîn û '''bikarhêner''' dikarin bi rêya appa wergerê, Microsoft Office û appa Bing, zêdetir ji 70 zimanên cihgirtî yên cîhanê yên di lîsteyê de bo zimanê kurdî û ji kurdî bo wan zimanan wergerînin.|malper=Xwebûn.org|url=https://xwebun.org/microsofte-kurdi-li-zimanen-xwe-yen-wergere-zede-kir|sernav=gerê ya wê kompaniyê de tên danîn|dîrok=24.08.2020}}
#* {{jêder-malper|ku|jêgirtin= Facebook û Google ragihandine, ku dê beşdariyê di kampîna cîhanî ya rûbirûbûna koronayê bibin û ji bo wê yekê jî dê dest bi hewldaneke cuda bikin û dê li ser torên civakî çavdêriya tevgera '''bikarhêneran''' bikin.|malper=Rûdaw|url=https://www.rudaw.net/kurmanci/sa7a/03052014|sernav= Li Ormiyê 13% ji bikarhênerên madeyên hişber jin in|dîrok=08.04.2020}}
# [[hînbûyî]]
#* {{jêder-malper|ku|jêgirtin=Hat zanîn, li parêzgeha Ormiyê ya Rojhilatê Kurdistanê rêjeya kesên ku madeyên hoşber bikar tînin roj bi roj zêde dibe û 13% ji '''bikarhênerên''' madeyên hişber jin in.|malper=Rûdaw|url=https://www.rudaw.net/kurmanci/sa7a/03052014|sernav= Li Ormiyê 13% ji bikarhênerên madeyên hişber jin in|dîrok=08.04.2020}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê û nêr}}
==== Formên alternatîv ====
* [[bikarêner]]
* [[bikarîner]]
* [[bikarhîner]]
* [[bikarber]]
==== Hevmane ====
* [[karpêker]]
* [[xebitîner]]
* [[şuxilîner]]
* [[şolpêker]]
==== Jê ====
* [[bikarhênerî]]
==== Etîmolojî ====
{{qertaf|ku|bi|kar|-hêner<t:[[-îner]]>}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Benutzer|n}}, {{W+|de|Verbraucher|n}}, {{W+|de|Abnehmer|m|guman=erê}}, {{W+|de|Betreiber|m|guman=erê}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|使用者}}
* {{Z|da}}: {{W+|da|bruger|c}}, {{W-|da|konsument|c}}
* {{Z|ar}}: {{W-|ar|مستخدم}} (mustexdim), {{W+|ar|مستعمل}} (muste'mil)
* {{Z|et}}: {{W-|et|tarbija}}
* {{Z|fa}}: {{W+|fa|کاربر}}
* {{Z|fi}}: {{W+|fi|käyttäjä}}, {{W+|fi|kuluttaja}}
* {{Z|fr}}: {{W+|fr|consommateur}}, {{W+|fr|utilisateur}}
* {{Z|en}}: {{W+|en|user}}, {{W+|en|consumer}}
* {{Z|it}}: {{W+|it|utente|n}}
* {{Z|is}}: {{W+|is|notandi|n}}
* {{Z|ja}}: {{W+|ja|使用者}}, {{W+|ja|ユーザー}}, {{W+|ja|ユーザ}}, {{W+|ja|利用者}}
* {{Z|lv}}: {{W+|lv|lietotājs|n}}, {{W-|lv|jūzeris|n}}
* {{Z|mk}}: {{W-|mk|корисник}}, {{W-|mk|потрошувач}}
* {{Z|hu}}: {{W+|hu|felhasználó}}
* {{Z|pl}}: {{W+|pl|użytkownik|n}}, {{W+|pl|użytkowniczka|m}}
* {{Z|pt}}: {{W+|pt|consumidor|n}}, {{W+|pt|usuário|n}}
* {{Z|ru}}: {{W|ru|tr=potrebítel'|потребитель|n}}, {{W|ru|tr=potrebítel'nica|потребительница|m}}, {{W+|ru|клиент|tr=klijént|n}}
* {{Z|sh}}: {{W+|sh|korisnik|n}}
* {{Z|sh}}: {{W+|sh|korisnik|n}}
* {{Z|sco}}: {{W-|sco|uiser}}
* {{Z|sl}}: {{W+|sl|uporabnik|n}}
* {{Z|sw}}: {{W+|sw|Mtumiaji}}
* {{Z|sv}}: {{W+|sv|användare|c}}, {{W+|sv|brukare|c}}
* {{Z|tr}}: {{W+|tr|kullanıcı}}, {{W-|tr|kullanan}}, {{W+|tr|tüketici}}
{{werger-bin}}
4ozbugenz2xyqswoclv72xg6h3ztqd9
firax
0
5094
6334522
6306701
2026-08-18T00:37:28Z
Wikihez
7344
6334522
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-firax.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Tiştên [[xwarin]] tê de tê çêkirin (''wek [[qazan]]'') <br>yan [[xwarin]] jê tê xwarin (''wek [[sênî]]'') <br>yan jî pê tê xwarin (''wek [[kefçî]]'')
#* {{J:Firat Cewerî:Payiza Dereng|jêgirtin=Xanima min jî di vê navê de ew texlît xwarinên ku min navên wan li jor hejmartin di '''firaxên''' porselen yên xweşik de li ser masê bi awayekî spehî bi cih kiribûn û mîna xanim û bermaliyeke bi xwe bawer li benda mêvanan bû.|weşanxane={{w|Avesta}}|sal=2014|r=16}}
# Her tiştê av yan xwarin tê de tê parastin.
#* {{jêder|ku|jêgirtin=Jin û mêr bi lez '''firaxên''' ku hebûn danîne ber dilopan, lê '''firaxên''' wan têra hemû dilopan nekirin, pir dilop bê '''firax''' man.|{{Jêgirtin/Firat Cewerî/Girtî}}}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|فراخ}}
==== Formên alternatîv ====
* {{g|ku|fera}}
* {{g|ku|firaq}}
==== Hevmane ====
* {{g|ku|aman}}
==== Jê ====
* firax [[şûştin]]
* firax [[ziwa]] kirin
* kirin firaxekê
* {{g|ku|firaxşo}}
* {{g|ku|firaxşok}}
* {{g|ku|firaxşokî}}
* {{g|ku|firaxşoyî}}
==== Etîmolojî ====
Ji {{ji|ira}}, hevreha [[fireh]], bi {{hv|fa|فراخ|tr=firax: [[fireh]]}}. Bi heman awayî bide ber {{nh|en|plate}} ([[sênîk]]) ji yûnanî [[πλατύς]] (platûs: [[fireh]]). Maneya "firax" jî divê berê "sênîk" be lê paşî bûye [[aman]].
==== Bide ber ====
* {{g|ku|ferax}}
==== Binêre herwiha ====
{{lîste:aman/ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Gefäß}} {{n}}, {{W|de|Geschirr}} {{n}}, {{W|de|Teller}} {{n}}, {{W|de|Assiette|f|guman=erê}}
* {{Z|ar}}: {{W|ar|إناء}}
* {{Z|fa}}: {{W|fa|ظرف}}
* {{Z|fr}}: {{W|fr|vaisselle}}
* {{Z|en}}: {{W|en|urn}}, {{W|en|vessel}}, {{W|en|pots and pans}}, {{W|en|dish}}
* {{Z|tr}}: {{W|tr|kap}}, {{W|tr|kapkacak}}, {{W|tr|kap kacak}}
{{werger-bin}}
531kgzeu5uc0vse1crc6p08uk1lpumz
6334524
6334522
2026-08-18T00:41:05Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6334524
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163 (kmr)-Ebulfîda-firax.wav|dever=Bidlîs}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# Tiştên [[xwarin]] tê de tê çêkirin (''wek [[qazan]]'') <br>yan [[xwarin]] jê tê xwarin (''wek [[sênî]]'') <br>yan jî pê tê xwarin (''wek [[kefçî]]'')
#* {{J:Firat Cewerî:Payiza Dereng|jêgirtin=Xanima min jî di vê navê de ew texlît xwarinên ku min navên wan li jor hejmartin di '''firaxên''' porselen yên xweşik de li ser masê bi awayekî spehî bi cih kiribûn û mîna xanim û bermaliyeke bi xwe bawer li benda mêvanan bû.|weşanxane={{w|Avesta}}|sal=2014|r=16}}
# Her tiştê av yan xwarin tê de tê parastin.
#* {{jêder|ku|jêgirtin=Jin û mêr bi lez '''firaxên''' ku hebûn danîne ber dilopan, lê '''firaxên''' wan têra hemû dilopan nekirin, pir dilop bê '''firax''' man.|{{Jêgirtin/Firat Cewerî/Girtî}}}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|فراخ}}
==== Formên alternatîv ====
* {{g|ku|fera}}
* {{g|ku|firaq}}
==== Hevmane ====
* {{g|ku|aman}}
==== Jê ====
* firax [[şûştin]]
* firax [[ziwa]] kirin
* kirin firaxekê
* {{g|ku|firaxşo}}
* {{g|ku|firaxşok}}
* {{g|ku|firaxşokî}}
* {{g|ku|firaxşoyî}}
==== Etîmolojî ====
Ji {{ji|ira}}, hevreha [[fireh]], bi {{hv|fa|فراخ|tr=firax: [[fireh]]}}. Bi heman awayî bide ber {{nh|en|plate}} ([[sênîk]]) ji yûnanî [[πλατύς]] (platûs: [[fireh]]). Maneya "firax" jî divê berê "sênîk" be lê paşî bûye [[aman]].
==== Bide ber ====
* {{g|ku|ferax}}
==== Herwiha binêre ====
{{lîste:aman/ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Gefäß}} {{n}}, {{W|de|Geschirr}} {{n}}, {{W|de|Teller}} {{n}}, {{W|de|Assiette|f|guman=erê}}
* {{Z|ar}}: {{W|ar|إناء}}
* {{Z|fa}}: {{W|fa|ظرف}}
* {{Z|fr}}: {{W|fr|vaisselle}}
* {{Z|en}}: {{W|en|urn}}, {{W|en|vessel}}, {{W|en|pots and pans}}, {{W|en|dish}}
* {{Z|tr}}: {{W|tr|kap}}, {{W|tr|kapkacak}}, {{W|tr|kap kacak}}
{{werger-bin}}
bmxq7wz1g77ef3p5vqtx1w9oh0wtkx5
aborî
0
5726
6334452
6296051
2026-08-17T23:24:54Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: sererastkirin
6334452
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163_(kmr)-Key Mîrza-aborî.wav|dever=Wan}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Billets_de_5000.jpg|thumb|[[pare|Pare]] di aboriyê de giring in.]]
{{navdêr|ku|z=m}}
# [[karûbar|Karûbar]]ên [[kirîn]] û [[firotin]]ê yên [[tevahî|tevahiya]] [[civak]]ekê.
#: {{hevmane|ku|ekonomî|iqtisad}}
#: {{mk|ku|'''Aboriya''' wî welatî gelek pêş ketiye.}}
# [[hêz|Hêz]]a kesekî ya pê bikare tiştan [[kirîn|bikire]].
#* {{J:Bûbê Eser:Gardiyan:2002|jêgirtin=Ji ber ko rewşa mala Sîdar ya '''aborî''' ne baş bû; wî him di daîreyeke dewletê de kar dikir û him jî unîversîteya xwe dixwend.}}
# [[bikaranîn|Bikaranîn]]a [[saman]] û [[sermiyan]]î bi awayekî wisa ku zêde xerc nebe lê dîsa jî [[mifa]]yek baş jê bêt wergirtin.
#* {{bêmînak|ku}}
# [[zanist|Zanist]]a [[aborî|aboriyê]].
#: {{hevmane|ku|aborînasî}}
#* {{bêmînak|ku}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|ئابۆری}}
* {{ku-kr|абори}}
==== Formên alternatîv ====
* {{g|ku|abûrî}}
* {{g|ku|eborî}}
* {{g|ku|ebûrî}}
==== Jê ====
* {{g|ku|aborînas}}
* {{g|ku|aborînasî}}
* {{g|ku|aborîvan}}
* {{g|ku|aborîvanî}}
* {{g|ku|aborîzan}}
* {{g|ku|aborîzanî}}
==== Etîmolojî ====
Hevreha soranî {{g|ckb|ئابووری}}, ji {{ji|sem}}, bo nimûne ji {{ji|arc|ku|ܥܒܘܪܐ}} (ibûra / ivûra: [[berhem]], [[ber]], [[dexlûdan]], [[xurak]], [[xwarin]], aborî, [[deramet]]) [https://web.archive.org/web/20210803192702/http://cal.huc.edu/searchroots.php?pos=N&lemma=(br], hevreha akadî [[𒂘]] (ebūru [BURU<sup>14</sup>]: [[xerman]])[http://www.assyrianlanguages.org/akkadian/dosearch.php?searchkey=eb%26%23363%3Bru&language=rawakkadian], {{hv|he|עבור}} (abor: [[ji ber]], [[bermayî]], [[berhem]]), têkilî erebî {{g|ar|عبر}} ('ebere: [[borîn]], [[derbas]] bûn). Ji eynî rehî belkî herwiha: [[borîn]] / [[buhirîn]] û [[borandin]] / [[buhirandin]] yên kurdî.
==== Bide ber ====
* {{g|ku|dara}}
==== Werger ====
{{werger-ser|iqtisad, birêvebirina serwet û dahatên civakekê}}
* {{Z|de}}: {{W+|de|Ökonomie|m}}
* {{Z|zgh}}: {{W-|zgh|ⵜⴰⴷⵎⵙⴰ|m}}
* {{Z|be}}: {{W-|be|экано́мія|m}}
* {{Z|bg}}: {{W+|bg|иконо́мика|m}}, {{W+|bg|стопа́нство|nt}}
* {{Z|bua}}: {{W-|bua|ажахы}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|經濟}}
* {{Z|da}}: {{W+|da|økonomi|g}}, {{W-|da|husholdning|g}}, {{W-|da|husholdning|g}}
* {{Z|ar}}: {{W+|ar|اِقْتِصاد|n}}
*: {{Z|acw}}: {{W-|acw|اِقْتِصاد|n|tr=igtiṣād}}
* {{Z|eo}}: {{W-|eo|ekonomio}}
* {{Z|fi}}: {{W+|fi|talous}}
* {{Z|ff}}:
*: Adlamî: {{W-|ff|𞤬𞤢𞤺𞥆𞤵𞤣𞤵}}
*: Latînî: {{W-|ff|faggudu}}
* {{Z|gd}}: {{W-|gd|eaconomaidh|n}}
* {{Z|nl}}: {{W+|nl|economie|m}}
* {{Z|io}}: {{W+|io|ekonomio}}
* {{Z|en}}: {{W+|en|economy}}
* {{Z|ga}}: {{W-|ga|barainneacht|m}}
* {{Z|it}}: {{W+|it|economia|m}}
* {{Z|is}}: {{W-|is|hagur|n}}
* {{Z|ja}}: {{W+|ja|経済|tr=けいざい, keizai}}
* {{Z|ca}}: {{W+|ca|economia|m}}
* {{Z|ko}}: {{W+|ko|경제}}
* {{Z|lv}}: {{W+|lv|ekonomika|m}}, {{W+|lv|saimniecība|m}}
* {{Z|ms}}: {{W+|ms|ekonomi}}
* {{Z|mi}}: {{W-|mi|ōhanga}}, {{W-|mi|taiōhanga}}
* {{Z|hu}}: {{W+|hu|gazdaság}}
* {{Z|mn}}:
*: Kirîlî: {{W-|mn|аж ахуй}}
* {{Z|oc}}: {{W+|oc|economia|m}}
* {{Z|pl}}: {{W+|pl|gospodarka|m}}
* {{Z|pt}}: {{W+|pt|economia|m}}
* {{Z|ru}}: {{W+|ru|эконо́мия|m}}
* {{Z|scn}}: {{W-|scn|acunumìa|m}}, {{W-|scn|cunumìa|m}}, {{W+|scn|ecunumìa|m}}
* {{Z|es}}: {{W+|es|economía|m}}
* {{Z|sw}}: {{W+|sw|uchumi}}
* {{Z|sv}}: {{W+|sv|ekonomi|g}}, {{W+|sv|hushållning|g}}
* {{Z|tl}}: {{W+|tl|ekonomiya}}
* {{Z|tr}}: {{W+|tr|ekonomi}}, {{W+|tr|iktisat}}, {{W-|tr|denlik}}
* {{Z|ur}}: {{W-|ur|مَعِیشَت|m|tr=ma'išat}}
* {{Z|uk}}: {{W+|uk|еконо́мія|m}}
* {{Z|el}}: {{W+|el|οικονομία|m}}
{{werger-bin}}
:{{werger çavkanî|en|economy|beş=Ev qismê Wergerê|oldid=75875500}}
=== Rengdêr ===
{{ku-tewîn-rd|aborî}}
{{rengdêr|ku}}
# [[iqtisadî]], [[ekonomîk]], [[ekonomîkî]], [[têkildarî]] [[aborî#Navdêr|aboriyê]]:
#: {{nimûne|ku|pirsên aborî, mijarên aborî, biryarên aborî}}
# [[erzan|Erzan]], ne [[giran]], ne [[bihadar]].
#: {{nimûne|ku|Min bilêtek '''aborî''' ya trênê kirrî.}}
==== Werger ====
{{werger-ser|iqtisadî, têkildarî aboriyê}}
* {{Z|sq}}: {{W+|sq|ekonomik}}
* {{Z|de}}: {{W+|de|ökonomisch}}, {{W+|de|Wirtschaft|cuda=Wirtschafts-}}
* {{Z|az}}: {{W-|az|iqtisadi}}
* {{Z|be}}: {{W-|be|эканамі́чны}}, {{W-|be|гаспада́рскi}}
* {{Z|bn}}: {{W-|bn|আর্থিক}}
* {{Z|br}}: {{W+|br|armerzhel}}
* {{Z|bg}}: {{W+|bg|икономи́чески}}, {{W-|bg|стопа́нски}}
* {{Z|my}}: {{W+|my|စီးပွားရေး}}
* {{Z|cs}}: {{W+|cs|hospodářský}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|經濟|tr=jīngjì}}
* {{Z|da}}: {{W-|da|økonomisk}}
* {{Z|ar}}: {{W-|ar|اِقْتِصَادِيّ}}
* {{Z|hy}}: {{W+|hy|տնտեսական}}
* {{Z|eo}}: {{W-|eo|ekonomia}}
* {{Z|et}}: {{W-|et|majanduslik}}
* {{Z|fa}}: {{W+|fa|اقتصادی|tr=eqtesâdi}}
* {{Z|fi}}: {{W+|fi|kansantaloudellinen}}
* {{Z|fr}}: {{W+|fr|économique}}
* {{Z|gd}}: {{W-|gd|eagonomach}}
* {{Z|gl}}: {{W+|gl|económico}}
* {{Z|ka}}: {{W-|ka|ეკონომიკური}}
* {{Z|hi}}: {{W+|hi|आर्थिक}}
* {{Z|nl}}: {{W+|nl|economisch}}
* {{Z|he}}: {{W-|he|כַּלְכָּלִי|tr=kalkalí}}
* {{Z|en}}: {{W+|en|economic}}
* {{Z|ga}}: {{W-|ga|eacnamaíoch}}, {{W-|ga|eacnamúil}}
* {{Z|it}}: {{W+|it|economico}}
* {{Z|is}}: {{W-|is|hagfræðilegur}}
* {{Z|ja}}: {{W+|ja|経済|tr=けいざいの, keizai no|cuda=経済の}}, {{W+|ja|経済的|tr=けいざいてきな, keizaiteki na|cuda=経済的な}}
* {{Z|ca}}: {{W+|ca|econòmic}}
* {{Z|ky}}: {{W+|ky|экономикалык}}
* {{Z|ko}}: {{W+|ko|경제|cuda=경제-의}}
* {{Z|lad}}: {{W-|lad|ekonomiko}}
* {{Z|lv}}: {{W-|lv|ekonomisks}}
* {{Z|lo}}: {{W+|lo|ເສດຖະກິດ}}
* {{Z|lt}}: {{W+|lt|ekonominis}}
* {{Z|mk}}: {{W-|mk|економски}}
* {{Z|ms}}: {{W+|ms|ekonomi}}
* {{Z|mt}}: {{W-|mt|ekonomiku}}
* {{Z|hu}}: {{W+|hu|gazdasági}}
* {{Z|mn}}:
*: Kirîlî: {{W-|mn|аж ахуй|cuda=аж ахуйн}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|økonomisk}}
* {{Z|oc}}: {{W+|oc|economic}}
* {{Z|ug}}: {{W-|ug|ئىقتىسادىي}}
* {{Z|uz}}: {{W+|uz|iqtisodiy}}
* {{Z|ps}}: {{W-|ps|وټه ييز|tr=wëṭa yiz}}, {{W+|ps|اقتصادي|tr=eqtesādi}}
* {{Z|pl}}: {{W+|pl|ekonomiczny}}, {{W+|pl|gospodarczy}}
* {{Z|pt}}: {{W+|pt|económico}}, {{W+|pt|econômico}}
* {{Z|kk}}: {{W-|kk|экономикалық}}
* {{Z|ro}}: {{W+|ro|economic}}
* {{Z|ru}}: {{W+|ru|экономи́ческий}}, {{W+|ru|хозя́йственный}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|еко̀номскӣ}}
*: Latînî: {{W+|sh|ekònomskī}}
* {{Z|sk}}: {{W+|sk|hospodársky}}
* {{Z|sl}}: {{W-|sl|ekonomski}}, {{W-|sl|gospodarski}}
* {{Z|es}}: {{W+|es|económico}}
* {{Z|sv}}: {{W+|sv|ekonomisk}}
* {{Z|tg}}: {{W-|tg|иқтисодӣ}}
* {{Z|tl}}: {{W-|tl|agimatin}}
* {{Z|th}}: {{W+|th|เศรษฐกิจ}}
* {{Z|tr}}: {{W+|tr|ekonomik}}, {{W+|tr|iktisadi}}
* {{Z|tr}}: {{W-|tr|مُعاشی|tr=mu'āśī}}, {{W-|ur|اِقْتِصادی|tr=iqtisādī}}
* {{Z|tk}}: {{W-|tk|ykdysady}}
* {{Z|uk}}: {{W+|uk|економі́чний}}, {{W-|uk|господа́рський}}
* {{Z|vi}}: {{W+|vi|kinh tế}}
* {{Z|km}}: {{W+|km|សេដ្ឋកិច្ច}}
* {{Z|yi}}: {{W-|yi|עקאָנאָמיש}}
* {{Z|el}}: {{W+|el|οικονομικός}}
{{werger-bin}}
:{{werger çavkanî|en|economic|beş=Ev qismê Wergerê|oldid=75961449}}
== {{ziman|zza}} ==
=== Navdêr ===
{{navdêr|ckb|sc=Latn}}
# aborî, [[ekonomî]], [[îqtîsad]]<ref>http://www.kovarabir.com/wp-content/uploads/Ferheng%C3%AA-Dimil%C3%AE-Zazak%C3%AE-%C3%BB-Kurmanc%C3%AE-A-a.pdf</ref>.
#* {{bêmînak|zza}}
=== Çavkanî ===
{{çavkanî}}
swtg9w688g55svraa8zvh2cl036bbcf
6334575
6334452
2026-08-18T01:12:10Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6334575
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{deng|ku|LL-Q36163_(kmr)-Key Mîrza-aborî.wav|dever=Wan}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Billets_de_5000.jpg|thumb|[[pare|Pare]] di aboriyê de giring in.]]
{{navdêr|ku|z=m}}
# [[karûbar|Karûbarên]] [[kirîn]] û [[firotin]]ê yên [[tevahî|tevahiya]] [[civak]]ekê.
#: {{hevmane|ku|ekonomî|iqtisad}}
#: {{mk|ku|'''Aboriya''' wî welatî gelek pêş ketiye.}}
# [[hêz|Hêza]] kesekî ya pê bikare tiştan [[kirîn|bikire]].
#* {{J:Bûbê Eser:Gardiyan:2002|jêgirtin=Ji ber ko rewşa mala Sîdar ya '''aborî''' ne baş bû; wî him di daîreyeke dewletê de kar dikir û him jî unîversîteya xwe dixwend.}}
# [[bikaranîn|Bikaranîna]] [[saman]] û [[sermiyan]]î bi awayekî wisa ku zêde xerc nebe lê dîsa jî [[mifa]]yek baş jê bêt wergirtin.
#* {{bêmînak|ku}}
# [[zanist|Zanista]] [[aborî|aboriyê]].
#: {{hevmane|ku|aborînasî}}
#* {{bêmînak|ku}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|ئابۆری}}
* {{ku-kr|абори}}
==== Formên alternatîv ====
* {{g|ku|abûrî}}
* {{g|ku|eborî}}
* {{g|ku|ebûrî}}
==== Jê ====
* {{g|ku|aborînas}}
* {{g|ku|aborînasî}}
* {{g|ku|aborîvan}}
* {{g|ku|aborîvanî}}
* {{g|ku|aborîzan}}
* {{g|ku|aborîzanî}}
==== Etîmolojî ====
Hevreha soranî {{g|ckb|ئابووری}}, ji {{ji|sem}}, bo nimûne ji {{ji|arc|ku|ܥܒܘܪܐ}} (ibûra / ivûra: [[berhem]], [[ber]], [[dexlûdan]], [[xurak]], [[xwarin]], aborî, [[deramet]]) [https://web.archive.org/web/20210803192702/http://cal.huc.edu/searchroots.php?pos=N&lemma=(br], hevreha akadî [[𒂘]] (ebūru [BURU<sup>14</sup>]: [[xerman]])[http://www.assyrianlanguages.org/akkadian/dosearch.php?searchkey=eb%26%23363%3Bru&language=rawakkadian], {{hv|he|עבור}} (abor: [[ji ber]], [[bermayî]], [[berhem]]), têkilî erebî {{g|ar|عبر}} ('ebere: [[borîn]], [[derbas]] bûn). Ji eynî rehî belkî herwiha: [[borîn]] / [[buhirîn]] û [[borandin]] / [[buhirandin]] yên kurdî.
==== Bide ber ====
* {{g|ku|dara}}
==== Werger ====
{{werger-ser|iqtisad, birêvebirina serwet û dahatên civakekê}}
* {{Z|de}}: {{W+|de|Ökonomie|m}}
* {{Z|zgh}}: {{W-|zgh|ⵜⴰⴷⵎⵙⴰ|m}}
* {{Z|be}}: {{W-|be|экано́мія|m}}
* {{Z|bg}}: {{W+|bg|иконо́мика|m}}, {{W+|bg|стопа́нство|nt}}
* {{Z|bua}}: {{W-|bua|ажахы}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|經濟}}
* {{Z|da}}: {{W+|da|økonomi|g}}, {{W-|da|husholdning|g}}, {{W-|da|husholdning|g}}
* {{Z|ar}}: {{W+|ar|اِقْتِصاد|n}}
*: {{Z|acw}}: {{W-|acw|اِقْتِصاد|n|tr=igtiṣād}}
* {{Z|eo}}: {{W-|eo|ekonomio}}
* {{Z|fi}}: {{W+|fi|talous}}
* {{Z|ff}}:
*: Adlamî: {{W-|ff|𞤬𞤢𞤺𞥆𞤵𞤣𞤵}}
*: Latînî: {{W-|ff|faggudu}}
* {{Z|gd}}: {{W-|gd|eaconomaidh|n}}
* {{Z|nl}}: {{W+|nl|economie|m}}
* {{Z|io}}: {{W+|io|ekonomio}}
* {{Z|en}}: {{W+|en|economy}}
* {{Z|ga}}: {{W-|ga|barainneacht|m}}
* {{Z|it}}: {{W+|it|economia|m}}
* {{Z|is}}: {{W-|is|hagur|n}}
* {{Z|ja}}: {{W+|ja|経済|tr=けいざい, keizai}}
* {{Z|ca}}: {{W+|ca|economia|m}}
* {{Z|ko}}: {{W+|ko|경제}}
* {{Z|lv}}: {{W+|lv|ekonomika|m}}, {{W+|lv|saimniecība|m}}
* {{Z|ms}}: {{W+|ms|ekonomi}}
* {{Z|mi}}: {{W-|mi|ōhanga}}, {{W-|mi|taiōhanga}}
* {{Z|hu}}: {{W+|hu|gazdaság}}
* {{Z|mn}}:
*: Kirîlî: {{W-|mn|аж ахуй}}
* {{Z|oc}}: {{W+|oc|economia|m}}
* {{Z|pl}}: {{W+|pl|gospodarka|m}}
* {{Z|pt}}: {{W+|pt|economia|m}}
* {{Z|ru}}: {{W+|ru|эконо́мия|m}}
* {{Z|scn}}: {{W-|scn|acunumìa|m}}, {{W-|scn|cunumìa|m}}, {{W+|scn|ecunumìa|m}}
* {{Z|es}}: {{W+|es|economía|m}}
* {{Z|sw}}: {{W+|sw|uchumi}}
* {{Z|sv}}: {{W+|sv|ekonomi|g}}, {{W+|sv|hushållning|g}}
* {{Z|tl}}: {{W+|tl|ekonomiya}}
* {{Z|tr}}: {{W+|tr|ekonomi}}, {{W+|tr|iktisat}}, {{W-|tr|denlik}}
* {{Z|ur}}: {{W-|ur|مَعِیشَت|m|tr=ma'išat}}
* {{Z|uk}}: {{W+|uk|еконо́мія|m}}
* {{Z|el}}: {{W+|el|οικονομία|m}}
{{werger-bin}}
:{{werger çavkanî|en|economy|beş=Ev qismê Wergerê|oldid=75875500}}
=== Rengdêr ===
{{ku-tewîn-rd|aborî}}
{{rengdêr|ku}}
# [[iqtisadî]], [[ekonomîk]], [[ekonomîkî]], [[têkildarî]] [[aborî#Navdêr|aboriyê]]:
#: {{nimûne|ku|pirsên aborî, mijarên aborî, biryarên aborî}}
# [[erzan|Erzan]], ne [[giran]], ne [[bihadar]].
#: {{nimûne|ku|Min bilêtek '''aborî''' ya trênê kirrî.}}
==== Werger ====
{{werger-ser|iqtisadî, têkildarî aboriyê}}
* {{Z|sq}}: {{W+|sq|ekonomik}}
* {{Z|de}}: {{W+|de|ökonomisch}}, {{W+|de|Wirtschaft|cuda=Wirtschafts-}}
* {{Z|az}}: {{W-|az|iqtisadi}}
* {{Z|be}}: {{W-|be|эканамі́чны}}, {{W-|be|гаспада́рскi}}
* {{Z|bn}}: {{W-|bn|আর্থিক}}
* {{Z|br}}: {{W+|br|armerzhel}}
* {{Z|bg}}: {{W+|bg|икономи́чески}}, {{W-|bg|стопа́нски}}
* {{Z|my}}: {{W+|my|စီးပွားရေး}}
* {{Z|cs}}: {{W+|cs|hospodářský}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|經濟|tr=jīngjì}}
* {{Z|da}}: {{W-|da|økonomisk}}
* {{Z|ar}}: {{W-|ar|اِقْتِصَادِيّ}}
* {{Z|hy}}: {{W+|hy|տնտեսական}}
* {{Z|eo}}: {{W-|eo|ekonomia}}
* {{Z|et}}: {{W-|et|majanduslik}}
* {{Z|fa}}: {{W+|fa|اقتصادی|tr=eqtesâdi}}
* {{Z|fi}}: {{W+|fi|kansantaloudellinen}}
* {{Z|fr}}: {{W+|fr|économique}}
* {{Z|gd}}: {{W-|gd|eagonomach}}
* {{Z|gl}}: {{W+|gl|económico}}
* {{Z|ka}}: {{W-|ka|ეკონომიკური}}
* {{Z|hi}}: {{W+|hi|आर्थिक}}
* {{Z|nl}}: {{W+|nl|economisch}}
* {{Z|he}}: {{W-|he|כַּלְכָּלִי|tr=kalkalí}}
* {{Z|en}}: {{W+|en|economic}}
* {{Z|ga}}: {{W-|ga|eacnamaíoch}}, {{W-|ga|eacnamúil}}
* {{Z|it}}: {{W+|it|economico}}
* {{Z|is}}: {{W-|is|hagfræðilegur}}
* {{Z|ja}}: {{W+|ja|経済|tr=けいざいの, keizai no|cuda=経済の}}, {{W+|ja|経済的|tr=けいざいてきな, keizaiteki na|cuda=経済的な}}
* {{Z|ca}}: {{W+|ca|econòmic}}
* {{Z|ky}}: {{W+|ky|экономикалык}}
* {{Z|ko}}: {{W+|ko|경제|cuda=경제-의}}
* {{Z|lad}}: {{W-|lad|ekonomiko}}
* {{Z|lv}}: {{W-|lv|ekonomisks}}
* {{Z|lo}}: {{W+|lo|ເສດຖະກິດ}}
* {{Z|lt}}: {{W+|lt|ekonominis}}
* {{Z|mk}}: {{W-|mk|економски}}
* {{Z|ms}}: {{W+|ms|ekonomi}}
* {{Z|mt}}: {{W-|mt|ekonomiku}}
* {{Z|hu}}: {{W+|hu|gazdasági}}
* {{Z|mn}}:
*: Kirîlî: {{W-|mn|аж ахуй|cuda=аж ахуйн}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|økonomisk}}
* {{Z|oc}}: {{W+|oc|economic}}
* {{Z|ug}}: {{W-|ug|ئىقتىسادىي}}
* {{Z|uz}}: {{W+|uz|iqtisodiy}}
* {{Z|ps}}: {{W-|ps|وټه ييز|tr=wëṭa yiz}}, {{W+|ps|اقتصادي|tr=eqtesādi}}
* {{Z|pl}}: {{W+|pl|ekonomiczny}}, {{W+|pl|gospodarczy}}
* {{Z|pt}}: {{W+|pt|económico}}, {{W+|pt|econômico}}
* {{Z|kk}}: {{W-|kk|экономикалық}}
* {{Z|ro}}: {{W+|ro|economic}}
* {{Z|ru}}: {{W+|ru|экономи́ческий}}, {{W+|ru|хозя́йственный}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|еко̀номскӣ}}
*: Latînî: {{W+|sh|ekònomskī}}
* {{Z|sk}}: {{W+|sk|hospodársky}}
* {{Z|sl}}: {{W-|sl|ekonomski}}, {{W-|sl|gospodarski}}
* {{Z|es}}: {{W+|es|económico}}
* {{Z|sv}}: {{W+|sv|ekonomisk}}
* {{Z|tg}}: {{W-|tg|иқтисодӣ}}
* {{Z|tl}}: {{W-|tl|agimatin}}
* {{Z|th}}: {{W+|th|เศรษฐกิจ}}
* {{Z|tr}}: {{W+|tr|ekonomik}}, {{W+|tr|iktisadi}}
* {{Z|tr}}: {{W-|tr|مُعاشی|tr=mu'āśī}}, {{W-|ur|اِقْتِصادی|tr=iqtisādī}}
* {{Z|tk}}: {{W-|tk|ykdysady}}
* {{Z|uk}}: {{W+|uk|економі́чний}}, {{W-|uk|господа́рський}}
* {{Z|vi}}: {{W+|vi|kinh tế}}
* {{Z|km}}: {{W+|km|សេដ្ឋកិច្ច}}
* {{Z|yi}}: {{W-|yi|עקאָנאָמיש}}
* {{Z|el}}: {{W+|el|οικονομικός}}
{{werger-bin}}
:{{werger çavkanî|en|economic|beş=Ev qismê Wergerê|oldid=75961449}}
== {{ziman|zza}} ==
=== Navdêr ===
{{navdêr|ckb|sc=Latn}}
# aborî, [[ekonomî]], [[îqtîsad]]<ref>http://www.kovarabir.com/wp-content/uploads/Ferheng%C3%AA-Dimil%C3%AE-Zazak%C3%AE-%C3%BB-Kurmanc%C3%AE-A-a.pdf</ref>.
#* {{bêmînak|zza}}
=== Çavkanî ===
{{çavkanî}}
c6a2je6va8k4mvrnhygtrc8901f7xrj
bersiv
0
8206
6334437
6299092
2026-08-17T23:12:32Z
Wikihez
7344
6334437
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[cewab]], [[welam]], [[îfade]], [[berdêl]]a [[pirs]]ekê, [[cab]], [[agahî|agahiya]] hatiye [[xwestin]]
#* {{J:Antoine de Saint-Exupéry:Mîrzayê Biçûk|jêgirtin=Hûn ê belkî ji xwe bipirsin: çima di vê pirtûkê de nigarine din wilo xweşik û dilbir wek nigara darên baobabê tune ne?" '''Bersiv''' hêsan e: min xwest lê ez çi bikim bi destê min derneket.}}
#: {{nm|ku|'''Bersiva''' pirsa min bide.|Wê agahiyê bide ya ku ji te dipirsim.|rêz=1}}
# [[çare]], [[hel]]
#: {{nm|ku|'''Bersivek''' bo vê pirsgirêkê divê.}}
# [[bertek]], [[reaksiyon]]
#: {{nm|ku|'''Bersiva''' kurdan çi ye dijî şewitandina gundan ji alî hikûmetê ve?}}
# [[rakirin]]a [[telefon]]ê
#: {{nm|ku|Min deh car telefona wî lê daye lê '''bersivê''' nade.}}
# [[hevberî]], [[berdêl]], [[bedel]]
#: {{nm|ku|Rizgarbûna Kurdistanê tek '''bersiv''' e dijî polîtîkayên asîmîlasyonê.}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|بهرسڤ}}
==== Formên alternatîv ====
{{kol3|ku
|bersif
|bersîv
}}
==== Jê ====
{{kol3|ku|cure=Jê
|bersiv dan<pos:lêker>
|bersiv xwestin<pos:lêker>
|bersivdan<pos:navdêr>
|bersivxwestin<pos:navdêr>
|bersivdayî<pos:rengdêr>
|bersivandin
|bersivandî
|bersivder
|bersivderî
|bersivîner
|bersivînerî
}}
==== Etîmolojî ====
Ji {{ji|ira-pro|ku|*paricáHwah}}.
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|antwoord}}
* {{Z|sq}}: {{W+|sq|përgjigje}}
* {{Z|de}}: {{W+|de|Antwort|m}}
* {{Z|alt}}: {{W-|alt|каруу}}, {{W-|alt|јооп}}
* {{Z|am}}: {{W-|am|መልስ}}
* {{Z|aii}}: {{W-|aii|ܓ݂ܘܼܝܵܒ݂ܵܐ|tr=ḡuyāwā|n}}, {{W-|aii|ܓ̰ܘܼܘܵܒܵܐ|tr=juwābā|n}}
* {{Z|agj}}: {{W-|agj|መልስ|tr=mäls}}
* {{Z|rup}}: {{W-|rup|apocrisi}}, {{W-|rup|apandisi}}, {{W-|rup|giuvapi|m}}
* {{Z|ast}}: {{W-|ast|respuesta|m}}
* {{Z|av}}: {{W-|av|жаваб}}
* {{Z|az}}: {{W+|az|cavab}}
* {{Z|eu}}: {{W-|eu|erantzun}}
* {{Z|ba}}: {{W-|ba|яуап}}
* {{Z|be}}: {{W-|be|адка́з|n}}, {{W-|be|во́дпаведзь|m}}
* {{Z|bal}}: {{W-|bal|جواب|tr=jawáb}}
* {{Z|bn}}: {{W+|bn|উত্তর}}, {{W+|bn|জবাব}}
* {{Z|bcl}}: {{W-|bcl|simbag}}
* {{Z|bi}}: {{W-|bi|ansa}}
* {{Z|br}}: {{W+|br|respont|n}}
* {{Z|bg}}: {{W+|bg|о́тговор|n}}
* {{Z|my}}: {{W+|my|အဖြေ}}
* {{Z|bua}}: {{W-|bua|харюу}}
* {{Z|ce}}: {{W-|ce|жоп}}
* {{Z|cs}}: {{W+|cs|odpověď|m}}
* {{Z|ny}}: {{W-|ny|yankho}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|回答|tr=wui4 dap3}}, {{W-|yue|對答|tr=deoi3 dap3}}
*: {{Z|dng}}: {{W-|dng|хуэйда}}, {{W-|dng|җәвабу}}
*: {{Z|cmn}}: {{W+|cmn|回答|tr=huídá}}, {{W-|cmn|對答|tr=duìdá}}
* {{Z|ckt}}: {{W-|ckt|татԓыӈ|tr=tatḷyṇ}}
* {{Z|da}}: {{W+|da|svar|nt}}
* {{Z|id}}: {{W+|id|jawaban}}
* {{Z|ar}}: {{W-|ar|جَوَاب|n}}, {{W-|ar|إِجَابَة|m}}
* {{Z|hy}}: {{W+|hy|պատասխան}}
* {{Z|eo}}: {{W+|eo|respondo}}
* {{Z|et}}: {{W-|et|vastus}}
* {{Z|gur}}: {{W-|gur|lergre}}
* {{Z|fa}}: {{W+|fa|جواب|tr=javâb}}, {{W+|fa|پاسخ|tr=pâsox}}, {{W-|fa|پیواژ|tr=peyvâž}}
* {{Z|fo}}: {{W-|fo|svar|nt}}
* {{Z|fi}}: {{W+|fi|vastaus}}
* {{Z|fr}}: {{W+|fr|réponse|m}}
* {{Z|gd}}: {{W-|gd|freagairt|m}}
* {{Z|gag}}: {{W-|gag|cuvap}}
* {{Z|gl}}: {{W+|gl|resposta|m}}
* {{Z|got}}: {{W-|got|𐌰𐌽𐌳𐌰𐍅𐌰𐌿𐍂𐌳𐌹|nt}}, {{W-|got|𐌰𐌽𐌳𐌰𐌷𐌰𐍆𐍄𐍃|m}}
* {{Z|gu}}: {{W-|gu|જવાબ}}, {{W+|gu|ઉત્તર}}
* {{Z|ka}}: {{W-|ka|პასუხი}}
* {{Z|ht}}: {{W-|ht|repons}}
* {{Z|haw}}: {{W-|haw|pane}}
* {{Z|mba}}: {{W-|mba|tubag}}
* {{Z|hi}}: {{W+|hi|जवाब|n}}, {{W+|hi|उत्तर|n}}
* {{Z|nl}}: {{W+|nl|antwoord|nt}}
* {{Z|he}}: {{W+|he|תְּשׁוּבָה|m|tr=t'shuvá}}
* {{Z|io}}: {{W+|io|respondo}}
* {{Z|ang}}: {{W-|ang|andwyrde|nt}}, {{W-|ang|andswaru|m}}
* {{Z|en}}: {{W+|en|answer}}, {{W+|en|response}}, {{W+|en|reply}}
* {{Z|ga}}: {{W+|ga|freagra|n}}
* {{Z|it}}: {{W+|it|risposta|m}}
* {{Z|ivv}}: {{W-|ivv|atbay}}
* {{Z|is}}: {{W+|is|svar|nt}}, {{W-|is|ans|nt}}
* {{Z|ja}}: {{W+|ja|答え|tr=こたえ, kotae}}, {{W+|ja|回答|tr=かいとう, kaitō}}, {{W+|ja|応答|tr=おうとう, ōtō}}
* {{Z|kaw}}: {{W-|kaw|wales}}
* {{Z|jv}}: {{W+|jv|jawab}}, {{W+|jv|wales}}
* {{Z|kl}}: {{W-|kl|akissut}}
* {{Z|xal}}: {{W-|xal|хәрү}}
* {{Z|pam}}: {{W-|pam|pakibat}}
* {{Z|krc}}: {{W-|krc|жауап}}
* {{Z|ca}}: {{W+|ca|resposta|m}}
* {{Z|ky}}: {{W+|ky|жооп}}
* {{Z|ko}}: {{W+|ko|대답}}, {{W-|ko|회답}}, {{W+|ko|응답}}, {{W+|ko|답변}}, {{W+|ko|답}}
* {{Z|kw}}: {{W-|kw|gorthyp}}
* {{Z|co}}: {{W+|co|risposta|m}}
* {{Z|kpy}}: {{W-|kpy|аӈъягыйӈын|tr=aŋyagəyŋən}}
* {{Z|kum}}: {{W-|kum|жавап}}
* {{Z|la}}: {{W-|la|responsum|nt}}
* {{Z|lv}}: {{W-|lv|atbilde|m}}
* {{Z|lo}}: {{W-|lo|ຄຳຕອບ}}
* {{Z|lez}}: {{W-|lez|жаваб}}
* {{Z|lij}}: {{W-|lij|rispósta}}
* {{Z|lt}}: {{W+|lt|atsakymas|n}}
* {{Z|lb}}: {{W+|lb|Äntwert|m}}
* {{Z|mk}}: {{W-|mk|одговор|n}}
* {{Z|ml}}: {{W+|ml|ഉത്തരം}}, {{W+|ml|മറുപടി}}
* {{Z|ms}}: {{W+|ms|jawaban}}, {{W+|ms|jawapan}}
* {{Z|mt}}: {{W-|mt|wieġeb|n}}
* {{Z|gv}}: {{W-|gv|ansoor|n}}, {{W-|gv|freggyrt|n}}
* {{Z|mi}}: {{W-|mi|whakautu}}
* {{Z|gun}}: {{W-|gun|mbovai}}
* {{Z|hu}}: {{W+|hu|válasz}}, {{W+|hu|felelet}}
* {{Z|mwl}}: {{W-|mwl|respuosta|m}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|хариу}}
* {{Z|nds}}: {{W+|nds|Antwoort|m}}
* {{Z|nog}}: {{W-|nog|явап}}
* {{Z|non}}: {{W-|non|svar|nt}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|svar|nt}}
*: {{Z|nn}}: {{W-|nn|svar|nt}}
* {{Z|oc}}: {{W+|oc|responsa|m}}
* {{Z|om}}: {{W-|om|deebii}}
* {{Z|ug}}: {{W-|ug|جاۋاب}}
* {{Z|uz}}: {{W+|uz|javob}}
* {{Z|ps}}: {{W+|ps|جواب|n|tr=ǰawãb}}, {{W+|ps|ځواب|n|tr=jwāb}}
* {{Z|pdt}}: {{W-|pdt|Auntwuat|m}}
* {{Z|pl}}: {{W+|pl|odpowiedź|m}}
* {{Z|pt}}: {{W+|pt|resposta|m}}
* {{Z|kk}}: {{W-|kk|жауап}}
* {{Z|ro}}: {{W+|ro|răspuns|nt}}
* {{Z|ru}}: {{W+|ru|отве́т|n}}, {{W+|ru|о́тповедь|m}}
* Sardinian:
*: {{Z|sc}}: {{W-|sc|rispòsta|m}}
* {{Z|se}}: {{W-|se|vástádus}}
* {{Z|ceb}}: {{W-|ceb|tubag}}
* {{Z|scn}}: {{W+|scn|risposta|m}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|о̏дгово̄р|n}}
*: Latînî: {{W+|sh|ȍdgovōr|n}}
* {{Z|cu}}:
*: Kirîlî: {{W-|cu|отъвѣтъ|n}}, {{W-|cu|отповѣдь|m}}, {{W-|cu|одповѣдь|m}}
* {{Z|sk}}: {{W-|sk|odpoveď|m}}
* {{Z|sl}}: {{W+|sl|odgovor|n}}
* {{Z|so}}: {{W+|so|jawaab}}
* {{Z|es}}: {{W+|es|respuesta|m}}
* {{Z|sw}}: {{W+|sw|jibu}}, {{W+|sw|jawabu}}
* {{Z|sv}}: {{W+|sv|svar|nt}}
* {{Z|tg}}: {{W-|tg|жавоб}}
* {{Z|tl}}: {{W-|tl|sagot}}
* {{Z|ta}}: {{W+|ta|பதில்}}
* {{Z|th}}: {{W+|th|คำตอบ}}
* {{Z|te}}: {{W+|te|జవాబు}}, {{W+|te|సమాధానం}}
* {{Z|crh}}: {{W-|crh|cevap}}
* {{Z|tt}}: {{W+|tt|җавап}}
* {{Z|tr}}: {{W+|tr|yanıt}}, {{W+|tr|cevap}}
* {{Z|tk}}: {{W+|tk|jogap}}
* {{Z|tyv}}: {{W-|tyv|харыы}}
* {{Z|ur}}: {{W-|ur|جواب|n|tr=javāb}}, {{W+|ur|اتر|tr=uttar}}
* {{Z|uk}}: {{W+|uk|ві́дповідь|m}}
* {{Z|vi}}: {{W+|vi|trả lời}}, {{W-|vi|câu trả lời}}
* {{Z|war}}: {{W-|war|baton}}, {{W-|war|tubag}}
* {{Z|cy}}: {{W+|cy|ateb}}
* {{Z|km}}: {{W-|km|ពាក្យឆ្លើយ}}
* {{Z|sah}}: {{W-|sah|эппиэт}}, {{W-|sah|харда}}, {{W-|sah|хоруй}}
* {{Z|yi}}: {{W-|yi|ענטפֿער|n}}
* {{Z|el}}: {{W+|el|απάντηση|m}}
*: Kevn: {{W-|grc|ἀπόκρισις|m}}, {{W-|grc|ὑπόκρισις|m}}{{werger-bin}}
:{{werger çavkanî|en|answer|beş=Ev beşa Wergerê|oldid=75403131}}
n2eh94ceu0wywyqsay1fmsskyad19pu
6334438
6334437
2026-08-17T23:12:45Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334438
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[cewab]], [[welam]], [[îfade]], [[berdêl]]a [[pirs]]ekê, [[cab]], [[agahî|agahiya]] hatiye [[xwestin]]
#: {{nm|ku|'''Bersiva''' pirsa min bide.|Wê agahiyê bide ya ku ji te dipirsim.|rêz=1}}
#* {{J:Antoine de Saint-Exupéry:Mîrzayê Biçûk|jêgirtin=Hûn ê belkî ji xwe bipirsin: çima di vê pirtûkê de nigarine din wilo xweşik û dilbir wek nigara darên baobabê tune ne?" '''Bersiv''' hêsan e: min xwest lê ez çi bikim bi destê min derneket.}}
# [[çare]], [[hel]]
#: {{nm|ku|'''Bersivek''' bo vê pirsgirêkê divê.}}
# [[bertek]], [[reaksiyon]]
#: {{nm|ku|'''Bersiva''' kurdan çi ye dijî şewitandina gundan ji alî hikûmetê ve?}}
# [[rakirin]]a [[telefon]]ê
#: {{nm|ku|Min deh car telefona wî lê daye lê '''bersivê''' nade.}}
# [[hevberî]], [[berdêl]], [[bedel]]
#: {{nm|ku|Rizgarbûna Kurdistanê tek '''bersiv''' e dijî polîtîkayên asîmîlasyonê.}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|بهرسڤ}}
==== Formên alternatîv ====
{{kol3|ku
|bersif
|bersîv
}}
==== Jê ====
{{kol3|ku|cure=Jê
|bersiv dan<pos:lêker>
|bersiv xwestin<pos:lêker>
|bersivdan<pos:navdêr>
|bersivxwestin<pos:navdêr>
|bersivdayî<pos:rengdêr>
|bersivandin
|bersivandî
|bersivder
|bersivderî
|bersivîner
|bersivînerî
}}
==== Etîmolojî ====
Ji {{ji|ira-pro|ku|*paricáHwah}}.
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|antwoord}}
* {{Z|sq}}: {{W+|sq|përgjigje}}
* {{Z|de}}: {{W+|de|Antwort|m}}
* {{Z|alt}}: {{W-|alt|каруу}}, {{W-|alt|јооп}}
* {{Z|am}}: {{W-|am|መልስ}}
* {{Z|aii}}: {{W-|aii|ܓ݂ܘܼܝܵܒ݂ܵܐ|tr=ḡuyāwā|n}}, {{W-|aii|ܓ̰ܘܼܘܵܒܵܐ|tr=juwābā|n}}
* {{Z|agj}}: {{W-|agj|መልስ|tr=mäls}}
* {{Z|rup}}: {{W-|rup|apocrisi}}, {{W-|rup|apandisi}}, {{W-|rup|giuvapi|m}}
* {{Z|ast}}: {{W-|ast|respuesta|m}}
* {{Z|av}}: {{W-|av|жаваб}}
* {{Z|az}}: {{W+|az|cavab}}
* {{Z|eu}}: {{W-|eu|erantzun}}
* {{Z|ba}}: {{W-|ba|яуап}}
* {{Z|be}}: {{W-|be|адка́з|n}}, {{W-|be|во́дпаведзь|m}}
* {{Z|bal}}: {{W-|bal|جواب|tr=jawáb}}
* {{Z|bn}}: {{W+|bn|উত্তর}}, {{W+|bn|জবাব}}
* {{Z|bcl}}: {{W-|bcl|simbag}}
* {{Z|bi}}: {{W-|bi|ansa}}
* {{Z|br}}: {{W+|br|respont|n}}
* {{Z|bg}}: {{W+|bg|о́тговор|n}}
* {{Z|my}}: {{W+|my|အဖြေ}}
* {{Z|bua}}: {{W-|bua|харюу}}
* {{Z|ce}}: {{W-|ce|жоп}}
* {{Z|cs}}: {{W+|cs|odpověď|m}}
* {{Z|ny}}: {{W-|ny|yankho}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|回答|tr=wui4 dap3}}, {{W-|yue|對答|tr=deoi3 dap3}}
*: {{Z|dng}}: {{W-|dng|хуэйда}}, {{W-|dng|җәвабу}}
*: {{Z|cmn}}: {{W+|cmn|回答|tr=huídá}}, {{W-|cmn|對答|tr=duìdá}}
* {{Z|ckt}}: {{W-|ckt|татԓыӈ|tr=tatḷyṇ}}
* {{Z|da}}: {{W+|da|svar|nt}}
* {{Z|id}}: {{W+|id|jawaban}}
* {{Z|ar}}: {{W-|ar|جَوَاب|n}}, {{W-|ar|إِجَابَة|m}}
* {{Z|hy}}: {{W+|hy|պատասխան}}
* {{Z|eo}}: {{W+|eo|respondo}}
* {{Z|et}}: {{W-|et|vastus}}
* {{Z|gur}}: {{W-|gur|lergre}}
* {{Z|fa}}: {{W+|fa|جواب|tr=javâb}}, {{W+|fa|پاسخ|tr=pâsox}}, {{W-|fa|پیواژ|tr=peyvâž}}
* {{Z|fo}}: {{W-|fo|svar|nt}}
* {{Z|fi}}: {{W+|fi|vastaus}}
* {{Z|fr}}: {{W+|fr|réponse|m}}
* {{Z|gd}}: {{W-|gd|freagairt|m}}
* {{Z|gag}}: {{W-|gag|cuvap}}
* {{Z|gl}}: {{W+|gl|resposta|m}}
* {{Z|got}}: {{W-|got|𐌰𐌽𐌳𐌰𐍅𐌰𐌿𐍂𐌳𐌹|nt}}, {{W-|got|𐌰𐌽𐌳𐌰𐌷𐌰𐍆𐍄𐍃|m}}
* {{Z|gu}}: {{W-|gu|જવાબ}}, {{W+|gu|ઉત્તર}}
* {{Z|ka}}: {{W-|ka|პასუხი}}
* {{Z|ht}}: {{W-|ht|repons}}
* {{Z|haw}}: {{W-|haw|pane}}
* {{Z|mba}}: {{W-|mba|tubag}}
* {{Z|hi}}: {{W+|hi|जवाब|n}}, {{W+|hi|उत्तर|n}}
* {{Z|nl}}: {{W+|nl|antwoord|nt}}
* {{Z|he}}: {{W+|he|תְּשׁוּבָה|m|tr=t'shuvá}}
* {{Z|io}}: {{W+|io|respondo}}
* {{Z|ang}}: {{W-|ang|andwyrde|nt}}, {{W-|ang|andswaru|m}}
* {{Z|en}}: {{W+|en|answer}}, {{W+|en|response}}, {{W+|en|reply}}
* {{Z|ga}}: {{W+|ga|freagra|n}}
* {{Z|it}}: {{W+|it|risposta|m}}
* {{Z|ivv}}: {{W-|ivv|atbay}}
* {{Z|is}}: {{W+|is|svar|nt}}, {{W-|is|ans|nt}}
* {{Z|ja}}: {{W+|ja|答え|tr=こたえ, kotae}}, {{W+|ja|回答|tr=かいとう, kaitō}}, {{W+|ja|応答|tr=おうとう, ōtō}}
* {{Z|kaw}}: {{W-|kaw|wales}}
* {{Z|jv}}: {{W+|jv|jawab}}, {{W+|jv|wales}}
* {{Z|kl}}: {{W-|kl|akissut}}
* {{Z|xal}}: {{W-|xal|хәрү}}
* {{Z|pam}}: {{W-|pam|pakibat}}
* {{Z|krc}}: {{W-|krc|жауап}}
* {{Z|ca}}: {{W+|ca|resposta|m}}
* {{Z|ky}}: {{W+|ky|жооп}}
* {{Z|ko}}: {{W+|ko|대답}}, {{W-|ko|회답}}, {{W+|ko|응답}}, {{W+|ko|답변}}, {{W+|ko|답}}
* {{Z|kw}}: {{W-|kw|gorthyp}}
* {{Z|co}}: {{W+|co|risposta|m}}
* {{Z|kpy}}: {{W-|kpy|аӈъягыйӈын|tr=aŋyagəyŋən}}
* {{Z|kum}}: {{W-|kum|жавап}}
* {{Z|la}}: {{W-|la|responsum|nt}}
* {{Z|lv}}: {{W-|lv|atbilde|m}}
* {{Z|lo}}: {{W-|lo|ຄຳຕອບ}}
* {{Z|lez}}: {{W-|lez|жаваб}}
* {{Z|lij}}: {{W-|lij|rispósta}}
* {{Z|lt}}: {{W+|lt|atsakymas|n}}
* {{Z|lb}}: {{W+|lb|Äntwert|m}}
* {{Z|mk}}: {{W-|mk|одговор|n}}
* {{Z|ml}}: {{W+|ml|ഉത്തരം}}, {{W+|ml|മറുപടി}}
* {{Z|ms}}: {{W+|ms|jawaban}}, {{W+|ms|jawapan}}
* {{Z|mt}}: {{W-|mt|wieġeb|n}}
* {{Z|gv}}: {{W-|gv|ansoor|n}}, {{W-|gv|freggyrt|n}}
* {{Z|mi}}: {{W-|mi|whakautu}}
* {{Z|gun}}: {{W-|gun|mbovai}}
* {{Z|hu}}: {{W+|hu|válasz}}, {{W+|hu|felelet}}
* {{Z|mwl}}: {{W-|mwl|respuosta|m}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|хариу}}
* {{Z|nds}}: {{W+|nds|Antwoort|m}}
* {{Z|nog}}: {{W-|nog|явап}}
* {{Z|non}}: {{W-|non|svar|nt}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|svar|nt}}
*: {{Z|nn}}: {{W-|nn|svar|nt}}
* {{Z|oc}}: {{W+|oc|responsa|m}}
* {{Z|om}}: {{W-|om|deebii}}
* {{Z|ug}}: {{W-|ug|جاۋاب}}
* {{Z|uz}}: {{W+|uz|javob}}
* {{Z|ps}}: {{W+|ps|جواب|n|tr=ǰawãb}}, {{W+|ps|ځواب|n|tr=jwāb}}
* {{Z|pdt}}: {{W-|pdt|Auntwuat|m}}
* {{Z|pl}}: {{W+|pl|odpowiedź|m}}
* {{Z|pt}}: {{W+|pt|resposta|m}}
* {{Z|kk}}: {{W-|kk|жауап}}
* {{Z|ro}}: {{W+|ro|răspuns|nt}}
* {{Z|ru}}: {{W+|ru|отве́т|n}}, {{W+|ru|о́тповедь|m}}
* Sardinian:
*: {{Z|sc}}: {{W-|sc|rispòsta|m}}
* {{Z|se}}: {{W-|se|vástádus}}
* {{Z|ceb}}: {{W-|ceb|tubag}}
* {{Z|scn}}: {{W+|scn|risposta|m}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|о̏дгово̄р|n}}
*: Latînî: {{W+|sh|ȍdgovōr|n}}
* {{Z|cu}}:
*: Kirîlî: {{W-|cu|отъвѣтъ|n}}, {{W-|cu|отповѣдь|m}}, {{W-|cu|одповѣдь|m}}
* {{Z|sk}}: {{W-|sk|odpoveď|m}}
* {{Z|sl}}: {{W+|sl|odgovor|n}}
* {{Z|so}}: {{W+|so|jawaab}}
* {{Z|es}}: {{W+|es|respuesta|m}}
* {{Z|sw}}: {{W+|sw|jibu}}, {{W+|sw|jawabu}}
* {{Z|sv}}: {{W+|sv|svar|nt}}
* {{Z|tg}}: {{W-|tg|жавоб}}
* {{Z|tl}}: {{W-|tl|sagot}}
* {{Z|ta}}: {{W+|ta|பதில்}}
* {{Z|th}}: {{W+|th|คำตอบ}}
* {{Z|te}}: {{W+|te|జవాబు}}, {{W+|te|సమాధానం}}
* {{Z|crh}}: {{W-|crh|cevap}}
* {{Z|tt}}: {{W+|tt|җавап}}
* {{Z|tr}}: {{W+|tr|yanıt}}, {{W+|tr|cevap}}
* {{Z|tk}}: {{W+|tk|jogap}}
* {{Z|tyv}}: {{W-|tyv|харыы}}
* {{Z|ur}}: {{W-|ur|جواب|n|tr=javāb}}, {{W+|ur|اتر|tr=uttar}}
* {{Z|uk}}: {{W+|uk|ві́дповідь|m}}
* {{Z|vi}}: {{W+|vi|trả lời}}, {{W-|vi|câu trả lời}}
* {{Z|war}}: {{W-|war|baton}}, {{W-|war|tubag}}
* {{Z|cy}}: {{W+|cy|ateb}}
* {{Z|km}}: {{W-|km|ពាក្យឆ្លើយ}}
* {{Z|sah}}: {{W-|sah|эппиэт}}, {{W-|sah|харда}}, {{W-|sah|хоруй}}
* {{Z|yi}}: {{W-|yi|ענטפֿער|n}}
* {{Z|el}}: {{W+|el|απάντηση|m}}
*: Kevn: {{W-|grc|ἀπόκρισις|m}}, {{W-|grc|ὑπόκρισις|m}}{{werger-bin}}
:{{werger çavkanî|en|answer|beş=Ev beşa Wergerê|oldid=75403131}}
ll3ihdnz733ah9z8h65ioaq3nkli34r
lazût
0
17013
6334366
6284028
2026-08-17T17:08:21Z
InternetArchiveBot
8979
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.5
6334366
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{wp}}
{{navdêr|ku|z=n}}
# [[garis]], [[genimok]], [[genimokê şamî]], [[şamî]]
#* {{bênimûne|ku}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|mais}}
* {{Z|fa}}: {{W+|fa|ذرت هندی}}
* {{Z|fi}}: {{W+|fi|maissi}}
* {{Z|fr}}: {{W+|fr|maïs}}
* {{Z|gl}}: {{W|gl|millo}}, {{W|gl|maínzo}}
* {{Z|en}}: {{W+|en|maize}}, {{W+|en|corn}}
* {{Z|it}}: {{W+|it|granturco}}, {{W+|it|mais}}
* lazî: [[lazut'i]]<ref>https://web.archive.org/web/20170719062742/http://www.lazuri.com/lazuri_ceviri/index.php</ref> (Lazî, zimanê bakûrê Anatolî ye)
* {{Z|ru}}: {{W+|ru|кукуруза}}, {{W+|ru|маис}}
* {{Z|es}}: {{W+|es|maíz}}
* {{Z|sv}}: {{W+|sv|majs}}
* {{Z|tr}}: {{W+|tr|darı}}, {{W+|tr|kokoroz}}, {{W+|tr|lazut}}, {{W+|tr|mısır}}
{{werger-bin}}
==== Çavkanî ====
{{çavkanî}}
tt9dpk28n29z0vtm36g19i4nyw68htk
cerg
0
21382
6334486
6291096
2026-08-17T23:55:43Z
Wikihez
7344
6334486
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:heart-and-lungs.jpg|thumb|230px|Dil û cerg.]]
{{navdêr|ku|z=m}}
# {{f|ku|anatomî}} [[organ|Organ]]a [[leş]]î ya [[xwarin]]ê [[helandin|dihelîne]] û [[jehr]]an [[ruxandin|diruxîne]].
#: {{hevmane|ku|cegera reş|mêlak|kezeba reş|pişa reş}}
#* {{Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan|sc=ku-Arab|jg=ظهر پشته سره ناڤک جوف ناڤ<br>کبد '''جرگه''' کلیه گرچک کدنه بز<br>روڤین امعاء مرارت هم ظراڤ|tr=Z̤ehr-i puşt e; sirre navik; cewf-i nav<br>Kebd-i '''cerg''' e; kulye gurçik; kidne bez<br>Rûviyin emʿaʾ; meraret hem z̤irav}}
#* {{jêder|ku|{{Jêgirtin/Ehmedê Xanî/Mem û Zîn}}|sc=ku-Arab|jg=خونی د دڤ و گپی وه هاڤیت<br>زینا کو دصد برین '''لجرگی'''<br>چو حالت فوت شادمرگی|tr=Xwînê di dev û gepê we havêt<br>Zîna ko diṣed birîn li '''cergê'''<br>Çû ḥalet-i fewt-i şadimergê}}
#* {{jêder|ku|{{Jêgirtin/Melayê Cizîrî/Dîwana Melayê Cizirî}}|sc=ku-Arab|jg=رنجیده کر مسکین غریب ترکا دل احباب دا<br>محبوب و ریحان پر گلاڤ کر کر لمن '''جرگ''' و هناڤ<br>زهرا هلاهل بو د ناڤ لو ساقی او جلاب دا|tr=Rencîde kir miskên x̣erîb; terka dil-i eḥbab-i da<br>Meḥbûb û reyḥan pir gulav; ker kir li min '''cerg''' û hinav<br>Zehra helahil bû di nav; lew saqî ew cullab-i da}}
# {{f|ku|anatomî}} [[organ|Organ]]a [[leş]]î ya [[nefes]] pê têt [[kişandin]].
#: {{hevmane|ku|cegera spî|pişa spî|kezeba spî}}
#* {{bênimûne|ku}}
# [[dil|Dil]], [[qelb]], [[hinav]].
#: {{nm|ku|'''Cerg''' li min sot.|Dilê min şewitand.|rêz=1}}
# {{f|ku|mecazî}} [[cesaret|Cesaret]], [[bistehî]], [[wêrekî]], [[curet]].
#* {{bênimûne|ku}}
# [[tê|Tê]] [[gotin]] [[bo]] [[kes]]ên gelek [[ezîz]] li def mirovî, [[herwiha]] bu [[zarok]]ên mirovî jî tê gotin.
#* {{bênimûne|ku}}
==== Bi alfabeyên din ====
* {{ku-ar|جەرگ}}
*: (''klasîk'') {{g|ku|sc=Arab|جرگ|tr=cerg}}
==== Bide ber ====
* [[pişik]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser|1}}
{{werger-bin}}
{{werger-ser|2}}
{{werger-bin}}
{{werger-ser|3}}
{{werger-bin}}
{{werger-ser|4}}
{{werger-bin}}
3aoiyry1g0klehou2a2fqoocpdzk6x8
6334491
6334486
2026-08-18T00:15:18Z
Wikihez
7344
6334491
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:heart-and-lungs.jpg|thumb|230px|Dil û cerg.]]
{{navdêr|ku|z=m}}
# {{f|ku|anatomî}} [[organ|Organ]]a [[leş]]î ya [[xwarin]]ê [[helandin|dihelîne]] û [[jehr]]an [[ruxandin|diruxîne]].
#: {{hevmane|ku|cegera reş|mêlak|kezeba reş|pişa reş}}
#* {{Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan|sc=ku-Arab|jg=ظهر پشته سره ناڤک جوف ناڤ<br>کبد '''جرگه''' کلیه گرچک کدنه بز<br>روڤین امعاء مرارت هم ظراڤ|tr=Z̤ehr-i puşt e; sirre navik; cewf-i nav<br>Kebd-i '''cerg''' e; kulye gurçik; kidne bez<br>Rûviyin emʿaʾ; meraret hem z̤irav}}
#* {{Jêgirtin/Ehmedê Xanî/Mem û Zîn|sc=Arab|jêgirtin=خونی د دڤ و گپی وه هاڤیت<br>زینا کو دصد برین '''لجرگی'''<br>چو حالت فوت شادمرگی|tr=Xwînê di dev û gepê we havêt<br>Zîna ko diṣed birîn li '''cergê'''<br>Çû ḥalet-i fewt-i şadimergê}}
#* {{Jêgirtin/Melayê Cizîrî/Dîwana Melayê Cizirî|sc=ku-Arab|jg=رنجیده کر مسکین غریب ترکا دل احباب دا<br>محبوب و ریحان پر گلاڤ کر کر لمن '''جرگ''' و هناڤ<br>زهرا هلاهل بو د ناڤ لو ساقی او جلاب دا|tr=Rencîde kir miskên x̣erîb; terka dil-i eḥbab-i da<br>Meḥbûb û reyḥan pir gulav; ker kir li min '''cerg''' û hinav<br>Zehra helahil bû di nav; lew saqî ew cullab-i da}}
# {{f|ku|anatomî}} [[organ|Organ]]a [[leş]]î ya [[nefes]] pê têt [[kişandin]].
#: {{hevmane|ku|cegera spî|pişa spî|kezeba spî}}
#* {{bênimûne|ku}}
# [[dil|Dil]], [[qelb]], [[hinav]].
#: {{nm|ku|'''Cerg''' li min sot.|Dilê min şewitand.|rêz=1}}
# {{f|ku|mecazî}} [[cesaret|Cesaret]], [[bistehî]], [[wêrekî]], [[curet]].
#* {{bênimûne|ku}}
# [[tê|Tê]] [[gotin]] [[bo]] [[kes]]ên gelek [[ezîz]] li def mirovî, [[herwiha]] bu [[zarok]]ên mirovî jî tê gotin.
#* {{bênimûne|ku}}
==== Bi alfabeyên din ====
* {{ku-ar|جەرگ}}
*: (''klasîk'') {{g|ku|sc=Arab|جرگ|tr=cerg}}
==== Bide ber ====
* [[pişik]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser|1}}
{{werger-bin}}
{{werger-ser|2}}
{{werger-bin}}
{{werger-ser|3}}
{{werger-bin}}
{{werger-ser|4}}
{{werger-bin}}
a1xctva9d3vsn6d1t6q2efo47wfbvyg
6334525
6334491
2026-08-18T00:41:08Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6334525
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:heart-and-lungs.jpg|thumb|230px|Dil û cerg.]]
{{navdêr|ku|z=m}}
# {{f|ku|anatomî}} [[organ|Organ]]a [[leş]]î ya [[xwarin]]ê [[helandin|dihelîne]] û [[jehr]]an [[ruxandin|diruxîne]].
#: {{hevmane|ku|cegera reş|mêlak|kezeba reş|pişa reş}}
#* {{Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan|sc=ku-Arab|jg=ظهر پشته سره ناڤک جوف ناڤ<br>کبد '''جرگه''' کلیه گرچک کدنه بز<br>روڤین امعاء مرارت هم ظراڤ|tr=Z̤ehr-i puşt e; sirre navik; cewf-i nav<br>Kebd-i '''cerg''' e; kulye gurçik; kidne bez<br>Rûviyin emʿaʾ; meraret hem z̤irav}}
#* {{Jêgirtin/Ehmedê Xanî/Mem û Zîn|sc=Arab|jêgirtin=خونی د دڤ و گپی وه هاڤیت<br>زینا کو دصد برین '''لجرگی'''<br>چو حالت فوت شادمرگی|tr=Xwînê di dev û gepê we havêt<br>Zîna ko diṣed birîn li '''cergê'''<br>Çû ḥalet-i fewt-i şadimergê}}
#* {{Jêgirtin/Melayê Cizîrî/Dîwana Melayê Cizirî|sc=ku-Arab|jg=رنجیده کر مسکین غریب ترکا دل احباب دا<br>محبوب و ریحان پر گلاڤ کر کر لمن '''جرگ''' و هناڤ<br>زهرا هلاهل بو د ناڤ لو ساقی او جلاب دا|tr=Rencîde kir miskên x̣erîb; terka dil-i eḥbab-i da<br>Meḥbûb û reyḥan pir gulav; ker kir li min '''cerg''' û hinav<br>Zehra helahil bû di nav; lew saqî ew cullab-i da}}
# {{f|ku|anatomî}} [[organ|Organ]]a [[leş]]î ya [[nefes]] pê têt [[kişandin]].
#: {{hevmane|ku|cegera spî|pişa spî|kezeba spî}}
#* {{bênimûne|ku}}
# [[dil|Dil]], [[qelb]], [[hinav]].
#: {{nm|ku|'''Cerg''' li min sot.|Dilê min şewitand.|rêz=1}}
# {{f|ku|mecazî}} [[cesaret|Cesaret]], [[bistehî]], [[wêrekî]], [[curet]].
#* {{bênimûne|ku}}
# [[tê|Tê]] [[gotin]] [[bo]] [[kes]]ên gelek [[ezîz]] li def mirovî, [[herwiha]] bu [[zarok]]ên mirovî jî tê gotin.
#* {{bênimûne|ku}}
==== Bi alfabeyên din ====
* {{ku-ar|جەرگ}}
*: (''klasîk'') {{g|ku|sc=Arab|جرگ|tr=cerg}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Bide ber ====
* [[pişik]]
==== Werger ====
{{werger-ser|1}}
{{werger-bin}}
{{werger-ser|2}}
{{werger-bin}}
{{werger-ser|3}}
{{werger-bin}}
{{werger-ser|4}}
{{werger-bin}}
p9hf1jv5e5vt4axqe97kxakbhxknj6m
6334574
6334525
2026-08-18T01:12:07Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6334574
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:heart-and-lungs.jpg|thumb|230px|Dil û cerg.]]
{{navdêr|ku|z=m}}
# {{f|ku|anatomî}} [[organ|Organa]] [[leş]]î ya [[xwarin]]ê [[helandin|dihelîne]] û [[jehr]]an [[ruxandin|diruxîne]].
#: {{hevmane|ku|cegera reş|mêlak|kezeba reş|pişa reş}}
#* {{Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan|sc=ku-Arab|jg=ظهر پشته سره ناڤک جوف ناڤ<br>کبد '''جرگه''' کلیه گرچک کدنه بز<br>روڤین امعاء مرارت هم ظراڤ|tr=Z̤ehr-i puşt e; sirre navik; cewf-i nav<br>Kebd-i '''cerg''' e; kulye gurçik; kidne bez<br>Rûviyin emʿaʾ; meraret hem z̤irav}}
#* {{Jêgirtin/Ehmedê Xanî/Mem û Zîn|sc=Arab|jêgirtin=خونی د دڤ و گپی وه هاڤیت<br>زینا کو دصد برین '''لجرگی'''<br>چو حالت فوت شادمرگی|tr=Xwînê di dev û gepê we havêt<br>Zîna ko diṣed birîn li '''cergê'''<br>Çû ḥalet-i fewt-i şadimergê}}
#* {{Jêgirtin/Melayê Cizîrî/Dîwana Melayê Cizirî|sc=ku-Arab|jg=رنجیده کر مسکین غریب ترکا دل احباب دا<br>محبوب و ریحان پر گلاڤ کر کر لمن '''جرگ''' و هناڤ<br>زهرا هلاهل بو د ناڤ لو ساقی او جلاب دا|tr=Rencîde kir miskên x̣erîb; terka dil-i eḥbab-i da<br>Meḥbûb û reyḥan pir gulav; ker kir li min '''cerg''' û hinav<br>Zehra helahil bû di nav; lew saqî ew cullab-i da}}
# {{f|ku|anatomî}} [[organ|Organa]] [[leş]]î ya [[nefes]] pê têt [[kişandin]].
#: {{hevmane|ku|cegera spî|pişa spî|kezeba spî}}
#* {{bênimûne|ku}}
# [[dil|Dil]], [[qelb]], [[hinav]].
#: {{nm|ku|'''Cerg''' li min sot.|Dilê min şewitand.|rêz=1}}
# {{f|ku|mecazî}} [[cesaret|Cesaret]], [[bistehî]], [[wêrekî]], [[curet]].
#* {{bênimûne|ku}}
# [[tê|Tê]] [[gotin]] [[bo]] [[kes]]ên gelek [[ezîz]] li def mirovî, [[herwiha]] bu [[zarok]]ên mirovî jî tê gotin.
#* {{bênimûne|ku}}
==== Bi alfabeyên din ====
* {{ku-ar|جەرگ}}
*: (''klasîk'') {{g|ku|sc=Arab|جرگ|tr=cerg}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Bide ber ====
* [[pişik]]
==== Werger ====
{{werger-ser|1}}
{{werger-bin}}
{{werger-ser|2}}
{{werger-bin}}
{{werger-ser|3}}
{{werger-bin}}
{{werger-ser|4}}
{{werger-bin}}
h0fzacx6rnxk7yxw7tjh3pr3uc7xgvr
pêşû
0
24125
6334355
6249743
2026-08-17T15:38:53Z
YeBoy371
7604
/* Werger */
6334355
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{IPA|ku|/pʰeːˈʃuː/}}
=== Navdêr ===
[[Wêne:Mosquito 2007-2.jpg|thumb|Pêşûyek xwînê dimêje.]]
{{navdêr|ku|z=m}}
# {{f|ku|buxik}} Hin [[heywan]]ên [[hûrik]] yên [[firok]] in û [[xwîn]]a mirovan û [[heywan]]ên din [[mêtin|dimêjin]].
#* {{bêmînak|ku}}
==== Bi alfabeyên din ====
* {{ku-ar|پێشوو}}
==== Formên alternatîv ====
* [[pêşwî]]
* [[pêşî]]
==== Etîmolojî ====
Herwiha ''pêşî'', [[hevreh]]a {{hv|ckb|پێشوولە|tr=pêşûle}}, {{hv|lki|پەخشە|tr=pexşe}}, {{hv|fa|پشه|tr=paše}}.
==== Bide ber ====
* [[kêç]]
* [[kêz]]
* [[mêrî]]
* [[mêş]]
* [[vizik]]
* [[mormok]]
* [[marmarok]]
==== Bi zaravayên din ====
* {{Z|ckb}}: {{W-|ckb|pêşûle|sc=Latn}}
* {{Z|lki}}: {{W-|lki|peşxe|sc=Latn}} , peşqe
==== Werger ====
{{werger-ser}}
* {{Z|ab}}: {{W-|ab|акәыбры}}
* {{Z|ace}}: {{W-|ace|jamok}}
* {{Z|adt}}: {{W-|adt|undhi}}
* {{Z|af}}: {{W-|af|muskiet}}
* {{Z|aho}}: {{W-|aho|𑜐𑜤𑜂𑜫}}
* {{Z|akl}}: {{W-|akl|namok}}
* {{Z|sq}}: {{W+|sq|mushkonjë}}
* {{Z|de}}: {{W+|de|Mücke|m}}, {{W+|de|Stechmücke|m}} ; {{W+|de|Schnake|m}} ; {{W+|de|Gelse|m}} ; {{W+|de|Moskito|n}}
* {{Z|am}}: {{W-|am|ትንኝ}}, {{W-|am|ወባ}}
* {{Z|amu}}: {{W-|amu|kíta}}
* {{Z|rup}}: {{W-|rup|mushconj|n}}, {{W-|rup|cunupi|m}}, {{W-|rup|tsãntsar|n}}
* {{Z|as}}: {{W-|as|মহ|sc=Beng}}
* {{Z|ast}}: {{W+|ast|mosquitu|n}}
* {{Z|av}}: {{W-|av|кӏкӏара}}
* {{Z|az}}: {{W+|az|ağcaqanad}}, {{W+|az|mığı}}
* {{Z|bdr}}: {{W-|bdr|namuk}}
* {{Z|ban}}: {{W-|ban|legu}}
* {{Z|bjn}}: {{W-|bjn|ñamuk}}
* {{Z|bjb}}: {{W-|bjb|goonoodyooloo}}
* {{Z|eu}}: {{W-|eu|eltxo}}
* {{Z|ba}}: {{W-|ba|серәкәй}}
* {{Z|sne}}: {{W-|sne|pirungang}}
* {{Z|be}}: {{W-|be|кама́р|n}}, {{W-|be|маскі́т|n}}
* {{Z|bn}}: {{W+|bn|মশা|sc=Beng}}
* Berberî:
*: {{Z|shi}}: {{W-|shi|wabiba|n}}
* {{Z|bcl}}: {{W-|bcl|namok}}
* {{Z|kxd}}: {{W-|kxd|rangit}}, {{W-|kxd|nyamuk}}
* {{Z|bdk}}: {{W-|bdk|мичек}}
* {{Z|bug}}: {{W-|bug|namo}}
* {{Z|mbb}}: {{W-|mbb|rengit}}
* {{Z|bg}}: {{W+|bg|кома́р|n}}
* {{Z|my}}: {{W+|my|ခြင်}}
* {{Z|bua}}: {{W-|bua|боргооһон}}
* {{Z|ccc}}: {{W-|ccc|ayno}}
* {{Z|ce}}: {{W-|ce|чуьрк}}
* {{Z|cs}}: {{W+|cs|komár|n}}
* {{Z|chr}}: {{W-|chr|ᏙᏌ}}
* {{Z|ny}}: {{W-|ny|udzudzu}}
* {{Z|cic}}: {{W-|cic|sapontaki'}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|蚊|tr=man1, man4}}
*: {{Z|dng}}: {{W-|dng|вынзы}}
*: {{Z|hak}}: {{W-|hak|蚊仔|tr=mûn-é}}
*: {{Z|cmn}}: {{W+|cmn|蚊子|tr=wénzi}}
*: {{Z|cdo}}: {{W-|cdo|風蚊|tr=hŭng-muòng}}
*: {{Z|nan}}: {{W+|nan|蠓|tr=báng}}, {{W-|nan|蚊仔|tr=báng-á}}
*: {{Z|wuu}}: {{W-|wuu|蚊子}}
* {{Z|ckt}}: {{W-|ckt|мранԓыӈын|tr=mranḷyṇyn}}, {{W-|ckt|мрэнти|p|tr=mrėnti}}
* {{Z|da}}: {{W-|da|stikmyg|g}}
* {{Z|xda}}: {{W-|xda|dyuping}}
* {{Z|xdk}}: {{W-|xdk|dura}}
* {{Z|dua}}: {{W-|dua|yungu}}
* {{Z|duf}}: {{W-|duf|no}}
* {{Z|duo}}: {{W-|duo|namok}}
* {{Z|id}}: {{W+|id|nyamuk}}
* {{Z|ar}}: {{W-|ar|بَعُوضَة|m}}, {{W-|ar|نَامُوسَة|m}}
*: {{Z|ajp}}: {{W-|ajp|ناموس|tr=nāmūs}}, {{W-|ar|ناموسة|m|tr=namusa}}
*: {{Z|acw}}: {{W-|acw|ناموس|tr=nāmūs}}, {{W-|ar|ناموسة|m|tr=namusa}}
*: Iraqi Arabic:{{W-|acm|بگ|tr=ḅagg}}
*: {{Z|api}}: {{W-|api|ناموس|tr=nāmūs}}, {{W-|ar|ناموسة|m|tr=namusa}}
*: {{Z|ajp}}: {{W-|ajp|ناموس|tr=nāmūs}}, {{W-|ajp|ناموسة|m|tr=nāmūsa, nāmūse}}
* {{Z|hy}}: {{W+|hy|մոծակ}}, {{W+|hy|մժեղ}}
* {{Z|eo}}: {{W+|eo|kulo}}, {{W+|eo|moskito}}
* {{Z|et}}: {{W+|et|sääsk}}, {{W-|et|moskiito}}
* {{Z|evn}}: {{W-|evn|ңанмакта}}
* {{Z|fa}}: {{W+|fa|پشه|tr=paše}}
* {{Z|fo}}: {{W-|fo|mýggjabiti|n}}, {{W-|fo|mýggjabit|nt}}, {{W-|fo|mýggj|nt}}
* {{Z|fj}}: {{W-|fj|namu}}
* {{Z|fi}}: {{W+|fi|hyttynen}}, {{W+|fi|sääski}}, {{W+|fi|moskiitto}}
* {{Z|fr}}: {{W+|fr|moustique|n}}
* {{Z|gd}}: {{W-|gd|mosgìoto|n}}
* {{Z|gl}}: {{W+|gl|mosquito|n}}
* {{Z|gu}}: {{W-|gu|મચ્છર|sc=Gujr}}
* {{Z|ka}}: {{W+|ka|კოღო|sc=Geor}}, {{W-|ka|ქინქლი|sc=Geor}}
* {{Z|ht}}: {{W-|ht|moustik}}
* {{Z|haw}}: {{W-|haw|makika}}
* {{Z|mba}}: {{W-|mba|tagunuk}}
* {{Z|hi}}: {{W+|hi|मच्छर|n|sc=Deva}}, {{W+|hi|मच्छड़|n}}, {{W+|hi|मशक|n}}
* {{Z|nl}}: {{W+|nl|mug|n|m}}, {{W+|nl|steekmug|m}}, {{W+|nl|muskiet|n}}
* {{Z|hrx}}: {{W-|hrx|Muskitt|n}}
* {{Z|he}}: {{W+|he|יַתּוּשׁ|n|tr=yatúsh}}, {{W-|he|כּוֹלְכִית|m|tr=kolchít}}
* {{Z|io}}: {{W+|io|moskito}}
* {{Z|ife}}: {{W-|ife|alákpá}}
* {{Z|ig}}: {{W-|ig|anwụ̄}}, {{W-|ig|anwụ ntà}}
* {{Z|ang}}: {{W-|ang|myċġ|n}}
* {{Z|en}}: {{W+|en|mosquito}}
* {{Z|inh}}: {{W-|inh|зунгат}}
* {{Z|ia}}: {{W-|ia|mosquito}}, {{W-|ia|culice}}
* {{Z|ga}}: {{W-|ga|muiscít|m}}
* {{Z|it}}: {{W+|it|zanzara|m}}
* {{Z|is}}: {{W+|is|moskítófluga|m}}
* {{Z|ja}}: {{W+|ja|蚊|tr=か, ka|sc=Jpan}}, {{W+|ja|カ|tr=ka}}
* {{Z|jv}}: {{W-|jv|lemut}}
* {{Z|jje}}: {{W-|jje|모기}}
* {{Z|kl}}: {{W-|kl|ippernaq}}
* {{Z|xal}}: {{W-|xal|бөкүн}}
* {{Z|kn}}: {{W-|kn|ನುಸಿಸೊಳ್ಳೆ|sc=Knda}}, {{W+|kn|ಸೊಳ್ಳೆ|sc=Knda}}
* {{Z|pam}}: {{W-|pam|amuk}}
* {{Z|ca}}: {{W+|ca|mosquit|n}}
* {{Z|zku}}: {{W-|zku|kuntipaitya}}
* {{Z|ky}}: {{W+|ky|чиркей|sc=Cyrl}}
* {{Z|ki}}: {{W-|ki|rwagĩ|c11}}
* {{Z|kqr}}: {{W-|kqr|rongit}}
* {{Z|kpv}}: {{W-|kpv|ном|sc=Cyrl}}
* {{Z|okm}}: {{W-|okm|모〮ᄀᆡ〮}}
* {{Z|ko}}: {{W+|ko|모기}}
* {{Z|kw}}: {{W-|kw|moskito|n}}
* {{Z|co}}: {{W+|co|zinzala|m}}
* {{Z|jam}}: {{W-|jam|maskitta}}
* {{Z|lad}}: {{W-|lad|moshkon}}, {{W-|lad|mushcón}}
* {{Z|lbe}}: {{W-|lbe|мичӏак}}
* {{Z|ltg}}: {{W-|ltg|ūds|n}}
* {{Z|la}}: {{W-|la|culex|n}}
* {{Z|lv}}: {{W+|lv|ods|n}}, {{W-|lv|dzēlējods|n}}, {{W-|lv|knislis|n}}, {{W-|lv|knausis|n}}, {{W-|lv|moskīts|n}}
* {{Z|lo}}: {{W+|lo|ຍຸງ}}
* {{Z|lez}}: {{W-|lez|мичек}}, {{W-|lez|ветӏ}}
* {{Z|olo}}: {{W-|olo|čakku}}, {{W-|olo|pinʼoi}}
* {{Z|lt}}: {{W+|lt|uodas|n}}, {{W-|lt|kuisys|n}}, {{W-|lt|varmas|n}}
* {{Z|khb}}: {{W-|khb|ᦍᦳᧂ}}
* {{Z|luo}}: {{W-|luo|suna}}
* {{Z|mad}}: {{W-|mad|reŋŋeʔ}}
* {{Z|mdh}}: {{W-|mdh|tagenek}}
* {{Z|mak}}: {{W-|mak|lamu}}
* {{Z|mk}}: {{W-|mk|комарец|n}}
* {{Z|mg}}: {{W+|mg|moka}}
* {{Z|ml}}: {{W-|ml|കൊതുക്|sc=Mlym}}
* {{Z|ms}}: {{W+|ms|nyamuk}}
* {{Z|mt}}: {{W-|mt|nemusa|m}}
* {{Z|mnc}}: {{W-|mnc|ᡤᠠᠯᠮᠠᠨ|tr=galman}}
* {{Z|gv}}: {{W-|gv|corveeyl|m}}
* {{Z|mi}}: {{W-|mi|waeroa}}, {{W-|mi|naenae}}, {{W-|mi|naeroa}}, {{W-|mi|ngaeroa}}, {{W-|mi|kēroa}}, {{W-|mi|namu katipō}}
* {{Z|mrw}}: {{W-|mrw|rengit}}
* {{Z|mr}}: {{W-|mr|डास}}
* Marî:
*: {{Z|mhr}}: {{W-|mhr|шыҥа}}
*: {{Z|mrj}}: {{W-|mrj|шӹнгӓ}}
* {{Z|mwr}}: {{W-|mwr|माछर}}
* {{Z|yua}}: {{W-|yua|k'oxol}}
* {{Z|hu}}: {{W+|hu|szúnyog}}
* {{Z|mic}}: {{W-|mic|glmuej|an}}
* {{Z|min}}: {{W-|min|ranŋiʔ}}
* {{Z|mdf}}: {{W-|mdf|сяське}}
* {{Z|mn}}: {{W+|mn|шумуул|sc=Cyrl}}, {{W+|mn|дэлэнч|sc=Cyrl}}
* Nahuatl:
*: Klasîk: {{W-|nci|mōyōtl}}
*: {{Z|azz}}: {{W-|azz|xajcalmo̱yo̱t}}
*: {{Z|nhx}}: {{W-|nhx|mo̱yo̱ꞌ}}
*: {{Z|ncl}}: {{W-|ncl|muyol}}
*: {{Z|ncj}}: {{W-|ncj|sancudo}}
*: {{Z|nhg}}: {{W-|nhg|sancuro}}
* {{Z|gld}}: {{W-|gld|гармакта}}
* {{Z|niq}}: {{W-|niq|kipcharkarario}}
* {{Z|nnr}}: {{W-|nnr|gunindi}}
* {{Z|nv}}: {{W-|nv|tsʼíʼii daʼaneezíʼ}}, {{W-|nv|tsʼíʼii}}
* {{Z|nay}}: {{W-|nay|muruli}}, {{W-|nay|muni}}
* {{Z|nrf}}: {{W-|nrf|moustique|m}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|mygg|n}}
* {{Z|ojw}}: {{W-|ojw|sakimê}}
* {{Z|oj}}: {{W-|oj|zagime}}
* {{Z|oc}}: {{W+|oc|moissal|n}}, {{W+|oc|mosquilh|n}}
* {{Z|om}}: {{W-|om|bookee}}
* {{Z|os}}: {{W-|os|къогъо}}
* {{Z|ota}}: {{W-|ota|سیوری سیڭك|tr=sivri siñek}}
* {{Z|ug}}: {{W-|ug|پاشا}}
* {{Z|uz}}: {{W+|uz|chivin}}, {{W+|uz|iskabtopar}}
* {{Z|pag}}: {{W-|pag|ageyet}}
* {{Z|pap}}: {{W-|pap|sangura}}
* {{Z|ps}}: {{W+|ps|غوماشه|m|tr=ǧumãša}}, {{W-|ps|مياسه|m|tr=myãsa}}, {{W-|ps|مياشه|m|tr=myãša}}
* {{Z|pjt}}: {{W-|pjt|kiwinyi}}
* {{Z|pl}}: {{W+|pl|komar|m-an}}
* {{Z|pt}}: {{W+|pt|mosquito|n}}, {{W+|pt|melga|m}}
* {{Z|pa}}: {{W-|pa|ਮੱਛਰ|sc=Guru}}
* {{Z|kk}}: {{W-|kk|маса|sc=Cyrl}}, {{W-|kk|москит|sc=Cyrl}}
* Khanty:
*: {{Z|kca}}: {{W-|kca|пєԓӈа|sc=Cyrl}}
* {{Z|rhg}}: {{W-|rhg|muca}}
* {{Z|rgn}}: {{W-|rgn|ẓanẓêla|m}}
* {{Z|ro}}: {{W+|ro|țânțar|n}}
* {{Z|ru}}: {{W+|ru|кома́р|n}}, {{W+|ru|моски́т|n}}
* {{Z|skb}}: {{W-|skb|ญุ๊ง}}
* {{Z|se}}: {{W-|se|čuoika}}
* {{Z|sm}}: {{W-|sm|namu}}
* {{Z|sgs}}: {{W-|sgs|koisis|n}}
* {{Z|sa}}: {{W+|sa|मशक|n}}
* {{Z|ceb}}: {{W-|ceb|lamok}}, {{W-|ceb|namok}}
* {{Z|scn}}: {{W-|scn|sampagghiuni|n}}, {{W+|scn|zappagghiuni|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|комарац|n}}, {{W-|sh|комарица|m}}
*: Latînî: {{W+|sh|komarac|n}}, {{W-|sh|komarica|m}}
* {{Z|sk}}: {{W+|sk|komár|n}}
* {{Z|sl}}: {{W+|sl|komar|n}}
* {{Z|es}}: {{W+|es|mosquito|n}}, {{W+|es|mosco|n}}, {{W+|es|plaga|m}}, {{W+|es|zancudo|n}}
* {{Z|su}}: {{W-|su|reungit}}
* {{Z|spp}}: {{W-|spp|lùpààn}}
* {{Z|sw}}: {{W+|sw|mbu|c9|c10}}
* {{Z|sv}}: {{W+|sv|mygga|g}}
* {{Z|shn}}: {{W-|shn|ယုင်း}}
* {{Z|shs}}: {{W-|shs|qwenímeqll}}
* {{Z|tyz}}: {{W-|tyz|mèng nhùng}}, {{W-|tyz|nhùng}}
* {{Z|tg}}: {{W-|tg|пашша}}, {{W+|tg|хомӯшак}}
* {{Z|mvv}}: {{W-|mvv|namuk}}, {{W-|mvv|tukung}}
* {{Z|tl}}: {{W-|tl|lamok}}
* {{Z|ta}}: {{W+|ta|கொசு}}, {{W+|ta|உலங்கு}},{{W+|ta|நுளம்பு}}, {{W+|ta|ஒலுங்கு}},{{W+|ta|சுள்ளான்}},{{W+|ta|கொதுகு}},{{W+|ta|முஞல்}}, {{W+|ta|மசம்}}
* {{Z|tsg}}: {{W-|tsg|hilam}}
* {{Z|nod}}: {{W-|nod|ᨿᩩᨦ}}
* {{Z|th}}: {{W+|th|ยุง}}
* {{Z|te}}: {{W+|te|దోమ}}
* {{Z|crh}}: {{W-|crh|şırqıy}}
* {{Z|tt}}: {{W+|tt|черки}}, {{W-|tt|озынборын}}
* {{Z|tet}}: {{W-|tet|susuk}}
* {{Z|ti}}: {{W-|ti|ጣንጡ}}
* {{Z|tr}}: {{W+|tr|sivrisinek}}
* {{Z|tk}}: {{W-|tk|çybyn}}, {{W+|tk|aýakçy}}, {{W-|tk|çirkeý}}
* {{Z|bo}}: {{W-|bo|དུག་སྦྲང}}
* {{Z|tyv}}: {{W-|tyv|ымыраа}}
* {{Z|udi}}: {{W-|udi|моцӏакӏ}}
* {{Z|udm}}: {{W-|udm|нымы}}, {{W-|udm|чибинь}}
* {{Z|ur}}: {{W-|ur|مچھر|n|tr=macchar|sc=ur-Arab}}
* {{Z|uk}}: {{W+|uk|кома́р|n}}, {{W-|uk|москі́т|n}}
* {{Z|uum}}: {{W-|uum|mığmığ}}
* {{Z|vec}}: {{W-|vec|sginsała|m}}, {{l|vec|sginzała}}, {{l|vec|sensała}}, {{l|vec|sansała}}
* {{Z|vi}}: {{W+|vi|muỗi}}
* {{Z|vo}}: {{W+|vo|muskit}}
* {{Z|cy}}: {{W-|cy|mosgito|n}}
* {{Z|wrh}}: {{W-|wrh|mugin}}
* {{Z|wo}}: {{W+|wo|yoo|cuda=yoo wi}}
* {{Z|ane}}: {{W-|ane|nôô}}
* {{Z|km}}: {{W+|km|មូស|tr=muuh|sc=Khmr}}
* {{Z|xh}}: {{W-|xh|ingcongconi|c9|c10}}
* {{Z|sah}}: {{W-|sah|бырдах}}, {{W-|sah|кумаар}}
* {{Z|yi}}: {{W-|yi|קאָמאַר|n|sc=Hebr}}, {{W-|yi|מאָסקיט|n}}
* {{Z|yo}}: {{W-|yo|ẹ̀fọn}}, {{W-|yo|yànmù-yánmú}}
* {{Z|el}}: {{W+|el|κουνούπι|nt|sc=Grek}}
* {{Z|za}}: {{W-|za|nyungz}}
* {{Z|zu}}: {{W+|zu|umiyane|c1a|c2a}}
{{werger-bin}}
:{{werger çavkanî|en|mosquito|beş=Ev qismê Wergerê|oldid=75873230}}
og4mlnkuxw81erfdvzz4m6719uy60am
çixare
0
26088
6334504
6322052
2026-08-18T00:22:41Z
Wikihez
7344
6334504
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|z=m}}
#{{guherto|cigare}}
#* {{J:Fawaz Husên:Siwarên Êşê:1994|jêgirtin=Wî pir ji çixarên amerîkayî en qûnpembo hez dikir{{...}}|r=}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
nbnoduns6f35espijfqt225qf4di07b
banyo
0
27509
6334401
6298079
2026-08-17T21:19:23Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: sererastkirin
6334401
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
[[Wêne:Bathroom in the Beamish Museum 01.JPG|thumb|150px|banyo (1)]]
{{navdêr|ku|z=m}}
# {{K|ku|ode}} [[ode|Ode]]ya ku mirov [[leş]]ê xwe tê ve [[şûştin|dişû]].
#: {{hevmane|ku|hemam|serşok|germaw|dûş}}
#: {{mk|ku|'''Banyo'''ya mala min pirr biçûk e lê paqij e.}}
#* {{jêder|ku|jêgirtin=Min bi lez rahişt makîneya seriyan û ez çûm '''banyo'''yê. Mîna ko ez bixwazim heyfê ji porê xwe hilînim, min ew gijomijo kir ko tam li ber çavên min reş bibe û min makîne da ser cênîka xwe ya rastê û heta bi qotê serê xwe, ez çûm.|Enwer Karahan: ''Serê min çi qasî dişibe yê bavê min'', Nefel.com, 6/2009}}
# [[şûştin|Şûştin]]a [[leş]]ê xwe.
#: {{mk|ku|Ev her [[spêde]] '''banyo'''yê dikim, [[taşt]]ê dixwim û paşî diçim ser karê xwe.}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din====
* {{ku-ar|بانیۆ}}
==== Jê ====
{{kol3|ku|cure=Jê
|banyo kirin<q:lêker>
|banyokirin<q:navdêr>
|banyokirî<q:rengdêr>
|banyoyî
}}
==== Etîmolojî ====
Ji {{deyn|it|ku|bagno}} (wek "banyo" tê xwendin) ji {{ji|la|ku|balneum}}, ku belkî hevreha [[balav]] ya kurdî ye.
==== Werger ====
{{werger-ser|odeya ku mirov xwe tê ve dişo}}
* {{Z|de}}: {{W+|de|Badezimmer|n}}, {{W+|de|Bad|n}}
* {{Z|br}}: {{W-|br|sal-gibellañ|f}}
* {{Z|cs}}: {{W+|cs|koupelna|f}}
* {{Z|eo}}: {{W+|eo|banĉambro}}
* {{Z|fa}}: {{W+|fa|حمام}}
* {{Z|fi}}: {{W+|fi|kylpyhuone}}
* {{Z|fr}}: {{W+|fr|salle de bain|f}}
* {{Z|nl}}: {{W+|nl|badkamer|f}}
* {{Z|en}}: {{W+|en|bathroom}}, {{W+|en|bath tub|guman=erê}}
* {{Z|it}}: {{W-|it|stanza da bagno|f}}, {{W+|it|bagno|m}}
* {{Z|ja}}: {{W-|ja|風呂場|tr=furoba}}, [[浴室]] (yokushitsu)
* {{Z|pl}}: {{W+|pl|łazienka|f}}
* {{Z|pt}}: {{W+|pt|banheiro|m}}
* {{Z|ro}}: {{W+|ro|baie|f}}
* {{Z|ru}}: {{W-|ru|ванная|tr=vánnaja}}, [[ванная комната]] (vánnaja kómnata)
* {{Z|sk}}: {{W-|sk|kúpelňa|f}}
* {{Z|sl}}: {{W+|sl|kopalnica|f}}
* {{Z|es}}: {{W+|es|cuarto de baño|m}}
* {{Z|sv}}: {{W+|sv|badrum|n}}
* {{Z|th}}: {{W-|th|ห้องน้ำ|tr=hông náam}}
* {{Z|tr}}: {{W+|tr|banyo}}
* {{Z|el}}: {{W-|el|λουτρό|tr=lutró}}, [[μπάνιο]] (bánio)
{{werger-bin}}
== {{ziman|tl}} ==
=== Navdêr ===
{{navdêr|tl}}
# {{mijar|tl|avahî}} {{g|ku|hemam}}
#* {{bêmînak|tl}}
=== Çavkanî ===
{{rûpela wergerê|tl|hemam}}
== {{ziman|tr}} ==
=== Bilêvkirin ===
* {{deng|tr|LL-Q256 (tur)-ToprakM-banyo.wav|Deng|dever=}}
=== Navdêr ===
{{navdêr|tr}}
# [[serşok]], [[hemamok]]
#* {{bêmînak|tr}}
# [[şerşok]], banyo
#* {{bêmînak|tr}}
# [[xweşûştin]], [[xwehemamkirin]]
#* {{bêmînak|tr}}
# ava biderman
#* {{bêmînak|tr}}
# banyo
#: {{mk|ku|güneş '''banyosu'''|xwe dan ber tavê, banyoya tavê|rêz=1}}
# banyo
#: {{mk|ku|fotoğraf '''banyosu'''|banyoya fotografan|rêz=1}}
0kerv2oimr3gpup6pdqmmnmz7bh0a83
6334436
6334401
2026-08-17T23:12:09Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir, Valahiya beşan rast kir.)
6334436
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
[[Wêne:Bathroom in the Beamish Museum 01.JPG|thumb|150px|banyo (1)]]
{{navdêr|ku|z=m}}
# {{K|ku|ode}} [[ode|Odeya]] ku mirov [[leş]]ê xwe tê ve [[şûştin|dişû]].
#: {{hevmane|ku|hemam|serşok|germaw|dûş}}
#: {{mk|ku|'''Banyo'''ya mala min pirr biçûk e lê paqij e.}}
#* {{jêder|ku|jêgirtin=Min bi lez rahişt makîneya seriyan û ez çûm '''banyo'''yê. Mîna ko ez bixwazim heyfê ji porê xwe hilînim, min ew gijomijo kir ko tam li ber çavên min reş bibe û min makîne da ser cênîka xwe ya rastê û heta bi qotê serê xwe, ez çûm.|Enwer Karahan: ''Serê min çi qasî dişibe yê bavê min'', Nefel.com, 6/2009}}
# [[şûştin|Şûştina]] [[leş]]ê xwe.
#: {{mk|ku|Ev her [[spêde]] '''banyo'''yê dikim, [[taşt]]ê dixwim û paşî diçim ser karê xwe.}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|بانیۆ}}
==== Jê ====
{{kol3|ku|cure=Jê
|banyo kirin<q:lêker>
|banyokirin<q:navdêr>
|banyokirî<q:rengdêr>
|banyoyî
}}
==== Etîmolojî ====
Ji {{deyn|it|ku|bagno}} (wek "banyo" tê xwendin) ji {{ji|la|ku|balneum}}, ku belkî hevreha [[balav]] ya kurdî ye.
==== Werger ====
{{werger-ser|odeya ku mirov xwe tê ve dişo}}
* {{Z|de}}: {{W+|de|Badezimmer|n}}, {{W+|de|Bad|n}}
* {{Z|br}}: {{W-|br|sal-gibellañ|f}}
* {{Z|cs}}: {{W+|cs|koupelna|f}}
* {{Z|eo}}: {{W+|eo|banĉambro}}
* {{Z|fa}}: {{W+|fa|حمام}}
* {{Z|fi}}: {{W+|fi|kylpyhuone}}
* {{Z|fr}}: {{W+|fr|salle de bain|f}}
* {{Z|nl}}: {{W+|nl|badkamer|f}}
* {{Z|en}}: {{W+|en|bathroom}}, {{W+|en|bath tub|guman=erê}}
* {{Z|it}}: {{W-|it|stanza da bagno|f}}, {{W+|it|bagno|m}}
* {{Z|ja}}: {{W-|ja|風呂場|tr=furoba}}, [[浴室]] (yokushitsu)
* {{Z|pl}}: {{W+|pl|łazienka|f}}
* {{Z|pt}}: {{W+|pt|banheiro|m}}
* {{Z|ro}}: {{W+|ro|baie|f}}
* {{Z|ru}}: {{W-|ru|ванная|tr=vánnaja}}, [[ванная комната]] (vánnaja kómnata)
* {{Z|sk}}: {{W-|sk|kúpelňa|f}}
* {{Z|sl}}: {{W+|sl|kopalnica|f}}
* {{Z|es}}: {{W+|es|cuarto de baño|m}}
* {{Z|sv}}: {{W+|sv|badrum|n}}
* {{Z|th}}: {{W-|th|ห้องน้ำ|tr=hông náam}}
* {{Z|tr}}: {{W+|tr|banyo}}
* {{Z|el}}: {{W-|el|λουτρό|tr=lutró}}, [[μπάνιο]] (bánio)
{{werger-bin}}
== {{ziman|tl}} ==
=== Navdêr ===
{{navdêr|tl}}
# {{mijar|tl|avahî}} {{g|ku|hemam}}
#* {{bêmînak|tl}}
=== Çavkanî ===
{{rûpela wergerê|tl|hemam}}
== {{ziman|tr}} ==
=== Bilêvkirin ===
* {{deng|tr|LL-Q256 (tur)-ToprakM-banyo.wav|Deng|dever=}}
=== Navdêr ===
{{navdêr|tr}}
# [[serşok]], [[hemamok]]
#* {{bêmînak|tr}}
# [[şerşok]], banyo
#* {{bêmînak|tr}}
# [[xweşûştin]], [[xwehemamkirin]]
#* {{bêmînak|tr}}
# ava biderman
#* {{bêmînak|tr}}
# banyo
#: {{mk|ku|güneş '''banyosu'''|xwe dan ber tavê, banyoya tavê|rêz=1}}
# banyo
#: {{mk|ku|fotoğraf '''banyosu'''|banyoya fotografan|rêz=1}}
2x1jcvsvxlgh3gg90vpq7ih20aeho6b
texmîn kirin
0
30378
6334471
5770234
2026-08-17T23:45:32Z
Wikihez
7344
6334471
wikitext
text/x-wiki
{{bnr-n|texmînkirin}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Lêker ===
{{ku-tewîn-lk|texmîn kirin|form=gerguhêz|niha=k|borî=kir|pêşgir=texmîn|valahî=erê}}
{{lêker|ku|c=h-gh}}
# [[hizir kirin]], [[pêşbînî kirin]], [[teqdîr kirin]]
#* {{jêder|ku|jêgirtin=Min '''texmîn kir''' ku wê ev pirtûk jî helbest be, lê gava min rahiştê (...) min dît ku ew bi pexşanê hatibû xemilandin.|{{Jêgirtin/Firat Cewerî/Girtî}}}}
#* {{J:Egîdê Xudo:Dê û Dêmarî:1995|jêgirtin=Tu jî jin î, tu yê '''texmîn bikî''', wekî çiqas zehmet e wextê dê ji ewledê xwe diqete.}}
==== Bi alfabeyên din ====
* {{ku-ar|تەخمین کرن}}
==== Etîmolojî ====
Ji {{heve|ku|texmîn|kirin}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|annehmen}}, {{W+|de|einschätzen}}, {{W+|de|feststellen}}, {{W+|de|raten}}, {{W+|de|schätzen}}, {{W+|de|tippen}}, {{W+|de|vermuten}}, {{W+|de|voraussehen}}, {{W+|de|abschätzen}}, {{W+|de|absehen}}, {{W+|de|erraten|guman=erê}}, {{W+|de|weissagen|guman=erê}}
* {{Z|ar}}: {{W-|ar|حسب}}, {{W-|ar|خمن}}
* {{Z|fa}}: {{W+|fa|برآورد کردن}}, {{W+|fa|گمان کردن}}, {{W+|fa|پیش بینی کردن}}, {{W+|fa|فرض کردن}}, {{W-|fa|تخمین زدن}}
* {{Z|en}}: {{W+|en|anticipate}}, {{W+|en|deem}}, {{W+|en|guess}}, {{W+|en|imagine}}, {{W+|en|suppose}}, {{W+|en|divinely}}, {{W+|en|estimate}}, {{W+|en|figure}}, {{W+|en|guesstimate|guman=erê}}, {{W+|en|ballpark figures|guman=erê}}, {{W+|en|prophesy|guman=erê}}, {{W+|en|look forward to|guman=erê}}
* {{Z|tr}}: {{W+|tr|kararlamak}}, {{W+|tr|kestirmek}}, {{W+|tr|oranlamak}}, {{W+|tr|ummak}}, {{W+|tr|tahmin etmek}}
{{werger-bin}}
q91w5eedni0mx19w2kk5dg4fu2jkytu
qeşmer
0
31466
6334442
6315827
2026-08-17T23:16:15Z
Wikihez
7344
6334442
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
<!-- * {{ku-IPA}} -->
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Smilie_2.JPG|thumb|250px|Qeşmerek (1)]]
{{navdêr|ku|z=mn}}
# {{f|ku|pîşe}} [[pêşkêşvan|Pêşkêşvan]] yan [[hunermend]]ê ku [[şow]]ên wisa dike daku [[temaşevan]] pê [[kenîn|bikenin]].
#* {{bênimûne|ku}}
# {{f|ku|mecazî}} Kesa/ê [[pêkenok|Pêkenok]], [[qerfî]], [[henekî]], [[tirraneyî]], [[tinazî]], [[zarveker]], pirraniya caran wesfeka nêgatîv.
#* {{J:Anton Çexov:Bexçeyê Vîşne|jêgirtin=Ew mirovekî xerab e; ji te pê ve her kes pê dizane; ew '''qeşmerekî''' kirêt e, pîs e...}}
#* {{jêder-pirtûk|ku|jêgirtin=Gur pîr dibe dibe '''qeşmerê''' rovî.|sal=1991|nivîskar=Hüseyin Deniz|url=http://books.google.com.tr/books?id=8hRlAAAAMAAJ&q=%22qe%C5%9Fmer%C3%AA%22&dq=%22qe%C5%9Fmer%C3%AA%22&hl=&cd=1&source=gbs_api|sernav=Gotnê pêşyê Kurda}}
#* {{jêder-pirtûk|ku|jêgirtin=“Na lo, tu lê binêr... ma ez '''qeşmerê''' te û bavê te me law?|sal=2014|nivîskar=Mazi Çiya|url=http://books.google.com.tr/books?id=BHBTAwAAQBAJ&pg=PT62&dq=%22qe%C5%9Fmer%C3%AA%22&hl=&cd=8&source=gbs_api|sernav=Bahol|weşanxane=Evrensel Basım Yayın|ISBN=9786054834969}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê û nêr}}
==== Formên alternatîv ====
* {{g|ku|qişmer}}
==== Jê ====
{{kol3|ku|cure=Jê
|qeşmerane
|qeşmerî
|qeşmerîtî
|qeşmerok
|qeşmertî
|qeşmer bûn<pos:lêker>
|qeşmer kirin<pos:lêker>
|qeşmerbûn<pos:navdêr>
|qeşmerkirin<pos:navdêr>
|qeşmerbûyî<pos:rengdêr>
|qeşmerkirî<pos:rengdêr>
}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|nar}}, {{W+|af|harlekyn}}, {{W+|af|paljas}}
* {{Z|sq}}: {{W+|sq|gaztor|n}}, {{W-|sq|klloun|n}}, {{W+|sq|palaço|n}}
* {{Z|de}}: {{W+|de|Clown|n}}, {{W+|de|Clownin|m}}
* {{Z|ast}}: {{W+|ast|payasu|n}}
* {{Z|az}}: {{W+|az|kloun}}, {{W-|az|oyunbaz}}
* {{Z|be}}: {{W-|be|клоўн|n}}
* {{Z|bg}}: {{W+|bg|кло́ун|n}}
* {{Z|cs}}: {{W-|cs|klaun|n}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|小丑|tr=xiǎochǒu}}
* {{Z|da}}: {{W+|da|klovn|g}}
* {{Z|id}}: {{W+|id|badut}}
* {{Z|ar}}: {{W-|ar|مُهَرِّج|n}}
*: {{Z|afb}}: {{W-|afb|ارقوز|n|tr=aragōz}}
*: {{Z|acw}}: {{W-|acw|مهرج|tr=muharrij}}, {{W-|acw|أرقوز|n|tr=aragōz}}
*: {{Z|arz}}: {{W-|arz|بلياتشو|tr=belyatšo}}, {{W-|arz|بهلوان|tr=bahlawān}}
*: {{Z|apc}}: {{W-|apc|مهرج|tr=muharrij}}
*: {{Z|ajp}}: {{W-|ajp|مهرج|tr=muharrij}}
* {{Z|hy}}: {{W+|hy|ծաղրածու}}
* {{Z|eo}}: {{W-|eo|klaŭno}}, {{W-|eo|klaŭnino}}
* {{Z|fa}}: {{W+|fa|دلقک|tr=dalqak}}
* {{Z|fo}}: {{W-|fo|íspegil|n}}, {{W-|fo|klovnur|n}}
* {{Z|fi}}: {{W+|fi|klovni}}, {{W+|fi|pelle}}
* {{Z|fr}}: {{W+|fr|clown|n}}, {{W+|fr|clownesse|m}}, {{W+|fr|pitre|n|m}}, {{W+|fr|bouffon|n}}, {{W+|fr|bouffonne|m}}
* {{Z|gl}}: {{W+|gl|pallaso|n}}
* {{Z|ka}}: {{W-|ka|კლოუნი}}
* {{Z|hi}}: {{W+|hi|विदूषक|n}}, {{W-|hi|जोकर|n}}
* {{Z|nl}}: {{W+|nl|clown|n}}
* {{Z|he}}: {{W+|he|לֵיצָן|n|tr=leitsán}}, {{W-|he|ליצנית|m|tr=leytsanit}}
* {{Z|io}}: {{W+|io|klauno}}
* {{Z|en}}: {{W+|en|clown}}
* {{Z|ga}}: {{W-|ga|áilteoir|n}}
* {{Z|it}}: {{W+|it|pagliaccio|n}}, {{W-|it|pagliaccia|m}}
* {{Z|is}}: {{W+|is|trúður|n}}
* {{Z|ja}}: {{W-|ja|ピエロ|tr=piero}}, {{W+|ja|道化師|tr=どうけし, dōkeshi}}
* {{Z|kl}}: {{W-|kl|quiasaarisartoq}}
* {{Z|ca}}: {{W+|ca|pallasso|n}}, {{W+|ca|pallassa|m}}
* {{Z|ky}}: {{W-|ky|клоун}}
* {{Z|ko}}: {{W+|ko|어릿광대}}, {{W+|ko|광대}}, {{W+|ko|피에로}}
* {{Z|la}}: {{W-|la|scurra|n}}, {{W-|la|sanniō|n}}, {{W-|la|balatrō|n}}
* {{Z|lv}}: {{W-|lv|klauns|n}}
* {{Z|mk}}: {{W-|mk|кловн|n}}, {{W-|mk|палја́чо|n}}
* {{Z|ml}}: {{W+|ml|കോമാളി}}
* {{Z|ms}}: {{W+|ms|badut}}
* {{Z|mr}}: {{W-|mr|जोकर|n}}
* {{Z|hu}}: {{W+|hu|bohóc}}
* {{Z|nv}}: {{W-|nv|łą́ʼátʼíinii}}, {{W-|nv|haashchʼéídóó}}
* {{Z|no}}:
*: {{Z|nb}}: {{W+|nb|klovn|n}}, {{W+|nb|bajas|n}}
*: {{Z|nn}}: {{W-|nn|klovn|n}}
* {{Z|oc}}: {{W+|oc|palhassa|n}}
* {{Z|uz}}: {{W-|uz|kloun}}, {{W+|uz|masxaraboz}}
* {{Z|pdt}}: {{W-|pdt|Spucht|n}}
* {{Z|pl}}: {{W+|pl|klaun|n}}, {{W+|pl|błazen|n}}
* {{Z|pt}}: {{W+|pt|palhaço|n}}, {{W+|pt|palhaça|m}}
* {{Z|kk}}: {{W-|kk|клоун}}
* {{Z|rm}}: {{W-|rm|clown|n}}
* {{Z|ro}}: {{W+|ro|clovn|n}}, {{W+|ro|bufon|n}}, {{W-|ro|bufonă|m}}, {{W+|ro|măscărici|n}}, {{W+|ro|paiață|m}}
* {{Z|ru}}: {{W+|ru|кло́ун|n}}, {{W+|ru|шут|n}}, {{W+|ru|пая́ц|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|клаун|n}}, {{W-|sh|кловн|n}}
*: Latînî: {{W+|sh|klaun|n}}, {{W+|sh|klovn|n}}
* {{Z|sk}}: {{W-|sk|klaun|n}}
* {{Z|sl}}: {{W+|sl|klovn|n}}, {{W-|sl|klovnesa|m}}
* {{Z|es}}: {{W+|es|payaso|n}}, {{W-|es|payasa|m}}, {{W+|es|clown|n}}, {{W+|es|clon|n}}
* {{Z|sv}}: {{W+|sv|clown|g}}, {{W+|sv|pajas|g}}
* {{Z|tg}}: {{W-|tg|масхарабоз}}
* {{Z|tl}}: {{W-|tl|payaso}}
* {{Z|th}}: {{W+|th|ตัวตลก}}
* {{Z|tr}}: {{W+|tr|palyaço}}
* {{Z|uk}}: {{W+|uk|кло́ун|n}}
* {{Z|vi}}: {{W+|vi|hề}}
* {{Z|cy}}: {{W-|cy|clown|n}}
* {{Z|yi}}: {{W-|yi|פּאַיאַץ|n}}
* {{Z|el}}: {{W+|el|κλόουν|n}}, {{W+|el|παλιάτσος|n}}, {{W+|el|γελωτοποιός|n}}
*: Kevn: {{W-|grc|γελωτοποιός|n}}
{{werger-bin}}
:{{werger çavkanî|en|clown|beş=Ev qismê Wergerê|oldid=76188434}}
7r65k0is7p4lr8o4or1vczjye7frkwt
muzîsyen
0
31743
6334306
6334295
2026-08-17T12:31:17Z
Wikihez
7344
6334306
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# Kesa/ê ku [[kar]]ê wî yan [[mijûlî|mijûliya]] wî [[muzîk]] e.
#: {{hevmane|ku|muzîkvan|muzîkjen|muzîkkar}}
#* {{jêder-websîte|ku|nivîskar=Jan Dost|sernav=جان دۆست (@JanDost99)|websîte=Twitter|url=https://x.com/JanDost99/status/1907902342587920623|tarîx=2025-04-03|jêgirtin=Bi '''muzîsyenê''' mezin Dilşad Seîd re.}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê û nêr}}
==== Formên alternatîv ====
* {{g|ku|muzisyen}}
* {{g|ku|mûzisyen}}
* {{g|ku|mûzîsyen}}
==== Jê ====
* {{g|ku|muzîsyenî}}
* {{g|ku|muzîsyenîtî}}
* {{g|ku|muzîsyentî}}
==== Têkildar ====
* {{g|ku|bilûrvan}}
* {{g|ku|deholvan}}
* {{g|ku|sazvan}}
* {{g|ku|tembûrvan}}
* {{g|ku|zirrnevan}}
==== Etîmolojî ====
Ji {{ji|fr|ku|musicien}}.
==== Werger ====
{{werger-ser}}
* {{Z|af}}: {{W+|af|musikant}}, {{W-|af|musikus}}, {{W+|af|musici|p}}
* {{Z|sq}}: {{W+|sq|muzikant|n}}
* {{Z|de}}: {{W+|de|Musiker|n}}, {{W+|de|Musikant|n}}, {{W+|de|Tonkünstler|n}}, {{W+|de|Musikus|n}}
* {{Z|ast}}: {{W-|ast|músicu|n}}
* {{Z|az}}: {{W-|az|musiqiçi}}
* {{Z|eu}}: {{W-|eu|musikari}}
* {{Z|be}}: {{W-|be|музы́ка|n|sc=Cyrl}}, {{W-|be|музыка́нт|n|sc=Cyrl}}
* {{Z|bn}}: {{W+|bn|বাদক}}
* {{Z|br}}: {{W+|br|soner|n}}, {{W-|br|sonour|n}}, {{W-|br|muzisian|n}}, {{W-|br|muziker|n}}
* {{Z|bg}}: {{W-|bg|музика́нт|n}}
* {{Z|my}}: {{W+|my|ဂီတသမား|sc=Mymr}}
* {{Z|ce}}: {{W-|ce|музыкант}}
* {{Z|cs}}: {{W+|cs|hudebník|n}}, {{W+|cs|muzikant|n}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|音樂家|tr=yīnyuèjiā}}
* {{Z|ckt}}: {{W-|ckt|эʼйӈэнэӈэткульын|tr=ėʼjṇėnėṇėtkulʹyn}}
* {{Z|da}}: {{W+|da|musiker|g}}
* {{Z|id}}: {{W+|id|musikus}}, {{W+|id|pemusik}}
* {{Z|ar}}: {{W+|ar|مُوسِيقِيّ|n}}
* {{Z|hy}}: {{W+|hy|երաժիշտ}}
* {{Z|eo}}: {{W+|eo|muzikisto}}
* {{Z|et}}: {{W+|et|muusik}}
* {{Z|fa}}: {{W+|fa|موزیسین|tr=muzisian}}, {{W-|fa|موزیکان|tr=muzikân}}, {{W+|fa|موسیقیدان|tr=musiqidân}}
* {{Z|fi}}: {{W+|fi|muusikko}}
* {{Z|fr}}: {{W+|fr|musicien|n}}, {{W+|fr|musicienne|m}}
* {{Z|fy}}: {{W-|fy|muzikus|n|m}}, {{W-|fy|muzikant|n}}, {{W-|fy|muzikante|m}}, {{W-|fy|muzykman|n}}
* {{Z|gd}}: {{W-|gd|ceòladair|n}}, {{W-|gd|neach-ciùil|n}}
* {{Z|gl}}: {{W+|gl|músico|n}}, {{W+|gl|música|m}}
* {{Z|ka}}: {{W-|ka|მუსიკოსი}}
* {{Z|hi}}: {{W+|hi|संगीतकार|n}}
* {{Z|nl}}: {{W+|nl|muzikant|n}}, {{W+|nl|muzikante|m}}, {{W+|nl|musicus|n|m}}
* {{Z|he}}: {{W-|he|מוּזִיקַאי|tr=muzikai}}
* {{Z|ang}}: {{W-|ang|dreāmere|n}}
* {{Z|enm}}: {{W-|enm|musicien}}, {{W-|enm|musiker}}, {{W-|enm|synger}}
* {{Z|en}}: {{W+|en|musician}}
* {{Z|ga}}: {{W-|ga|ceoltóir|n}}
* {{Z|it}}: {{W+|it|musicista|n|m}}, {{W+|it|musico|n}}
* {{Z|is}}: {{W+|is|tónlistarmaður|n}}
* {{Z|ja}}: {{W+|ja|音楽家|tr=おんがくか, ongakuka}}
* {{Z|jv}}: {{W-|jv|niyógo}}
* {{Z|kl}}: {{W-|kl|nipilersortartoq}}
* {{Z|xal}}: {{W-|xal|көгҗмч|sc=Cyrl}}
* {{Z|ca}}: {{W+|ca|músic|n}}
* {{Z|ky}}: {{W+|ky|музыкант|sc=Cyrl}}
* {{Z|ko}}: {{W+|ko|음악가}}
* {{Z|kw}}: {{W-|kw|ilewydh|n}}, {{W-|kw|ilewydhes|m}}
* {{Z|co}}: {{W-|co|musicante|n|m}}
* {{Z|la}}: {{W-|la|mūsicus|n}}, {{W-|la|mūsica|m}}
* {{Z|lv}}: {{W-|lv|mūziķis|n}}
* {{Z|lo}}: {{W+|lo|ນັກດົນຕີ}}
* {{Z|lij}}: {{W-|lij|mûxicante|n}}
* {{Z|lt}}: {{W-|lt|muzikantas|n}}, {{W-|lt|muzikas|n}}
* {{Z|lmo}}: {{W-|lmo|musicista|n}}
* {{Z|lb}}: {{W-|lb|Museker|n}}, {{W-|lb|Musekerin|m}}
* {{Z|mk}}: {{W-|mk|му́зичар|n}}
* {{Z|ms}}: {{W-|ms|pemuzik}}
* {{Z|mi}}: {{W-|mi|kaiwhakatangi}}, {{W+|mi|kai}}, {{W-|mi|kaipuoro}}, {{W-|mi|kiripuoro}}
* {{Z|hu}}: {{W+|hu|zenész}}, {{W+|hu|muzsikus}}
* {{Z|mn}}: {{W+|mn|хөгжимчин|sc=Cyrl}}
* {{Z|nhg}}: {{W-|nhg|mursi̱co}}
* {{Z|nrf}}: {{W-|nrf|musicien|n}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|musiker|n}}
*: {{Z|nn}}: {{W-|nn|musikar|n}}
* {{Z|uz}}: {{W+|uz|musiqachi}}, {{W+|uz|muzikant}}
* {{Z|pi}}: {{W-|pi|vādaka|n}}
* {{Z|pms}}: {{W-|pms|musicista|n}}
* {{Z|pl}}: {{W+|pl|muzyk|m-pr}}, {{W+|pl|muzyczka|m}}
* {{Z|pt}}: {{W+|pt|músico|n}}
* {{Z|kk}}: {{W-|kk|музыкант|sc=Cyrl}}
* {{Z|rgn}}: {{W-|rgn|mușicânt|n}}
* {{Z|ro}}: {{W+|ro|muzician|n}}, {{W+|ro|muziciană|m}}
* {{Z|ru}}: {{W-|ru|музыка́нт|n}}, {{W-|ru|музыка́нтка|m|sc=Cyrl}}
* {{Z|sa}}: {{W+|sa|वादक|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|музичар|n|sc=Cyrl}}
*: Latînî: {{W+|sh|muzičar|n}}, {{W+|sh|glazbenik|n}}
* {{Z|sco}}: {{W-|sco|muisicker}}, {{W-|sco|muisician}}
* {{Z|sk}}: {{W-|sk|hudobník|n}}, {{W-|sk|muzikant|n}}
* {{Z|sl}}: {{W+|sl|glasbenik|n}}, {{W-|sl|glasbenica|m}}
* {{Z|st}}: {{W-|st|sebini}}
* {{Z|es}}: {{W+|es|músico|n}}
* {{Z|sw}}: {{W+|sw|mwimbaji}}, {{W+|sw|mwanamuziki}}
* {{Z|sv}}: {{W+|sv|musikant|g}}, {{W+|sv|musiker|g}}
* {{Z|tg}}: {{W-|tg|мусиқачӣ}}
* {{Z|tl}}: {{W-|tl|musiko}}
* {{Z|rif}}: {{W-|rif|amedyaz|n}}
* {{Z|th}}: {{W-|th|นักดนตรี}}
* {{Z|tr}}: {{W+|tr|müzisyen}}
* {{Z|tk}}: {{W-|tk|sazandar}}, {{W-|tk|sazanda}}
* {{Z|ur}}: {{W-|ur|موسیقار|n|tr=musiqār|sc=ur-Arab}}
* {{Z|uk}}: {{W-|uk|музика́нт|n}}
* {{Z|vi}}: {{W+|vi|nhạc sĩ}}
* {{Z|vo}}: {{W+|vo|musigan}}
* {{Z|cy}}: {{W+|cy|cerddwr|n}}
* {{Z|km}}: {{W-|km|អ្នកភ្លេង}}, {{W+|km|វាទកៈ}}
* {{Z|yi}}: {{W-|yi|מוזיקער|n}}
* {{Z|el}}: {{W+|el|μουσικός|n|m}}
*: Kevn: {{W-|grc|μουσικός|n}}
{{werger-bin}}
:{{werger çavkanî|en|musician|beş=Ev qismê Wergerê|oldid=75678903}}
{{mijar|ku|muzîsyen|kes}}
2e5c2id1usd96uo0nidvrj2w4mkzv45
destkirin
0
32437
6334405
5796613
2026-08-17T21:26:25Z
Wikihez
7344
6334405
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m|lk=dest kirin}}
# {{guharto|ku|dest kirin}}
==== Bi alfabeyên erebî ====
* {{ku-ar|دەستکرن}}
==== Etîmolojî ====
Ji {{pêk|ku|dest|kirin}}.
==== Werger ====
{{werger-ser}}
* {{Z|ar}}: {{W|ar|اللمس|guman=erê}}, {{W|ar|الملامسة|guman=erê}}, {{W|ar|مسیس|guman=erê}}
* {{Z|fa}}: {{W+|fa|دستاورد}}
* {{Z|en}}: {{W+|en|achievement|guman=erê}}
{{werger-bin}}
80rrmlw1zl2bde5qff2gb6k85n1ea91
6334406
6334405
2026-08-17T21:26:51Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334406
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m|lk=dest kirin}}
# {{mane?|ku}}
==== Bi alfabeyên erebî ====
* {{ku-ar|دەستکرن}}
==== Etîmolojî ====
Ji {{pêk|ku|dest|kirin}}.
==== Werger ====
{{werger-ser}}
* {{Z|ar}}: {{W|ar|اللمس|guman=erê}}, {{W|ar|الملامسة|guman=erê}}, {{W|ar|مسیس|guman=erê}}
* {{Z|fa}}: {{W+|fa|دستاورد}}
* {{Z|en}}: {{W+|en|achievement|guman=erê}}
{{werger-bin}}
gusnff6tyzfn646bdmznk0f8nl7xf3l
6334409
6334406
2026-08-17T21:41:06Z
Balyozbot
7867
[[Special:PermanentLink/6298023|Bot]]: rêzkirina beşan
6334409
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m|lk=dest kirin}}
# {{mane?|ku}}
==== Etîmolojî ====
Ji {{pêk|ku|dest|kirin}}.
==== Bi alfabeyên erebî ====
* {{ku-ar|دەستکرن}}
==== Werger ====
{{werger-ser}}
* {{Z|ar}}: {{W|ar|اللمس|guman=erê}}, {{W|ar|الملامسة|guman=erê}}, {{W|ar|مسیس|guman=erê}}
* {{Z|fa}}: {{W+|fa|دستاورد}}
* {{Z|en}}: {{W+|en|achievement|guman=erê}}
{{werger-bin}}
gq7140gefbnteyx5mu0041wqzhkwudg
6334413
6334409
2026-08-17T22:33:08Z
Wikihez
7344
/* Kurmancî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334413
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m|lk=dest kirin}}
# {{mane?|ku}}
==== Bi alfabeyên din ====
* {{ku-ar|دەستکرن}}
==== Etîmolojî ====
Ji {{pêk|ku|dest|kirin}}.
==== Werger ====
{{werger-ser}}
* {{Z|ar}}: {{W|ar|اللمس|guman=erê}}, {{W|ar|الملامسة|guman=erê}}, {{W|ar|مسیس|guman=erê}}
* {{Z|fa}}: {{W+|fa|دستاورد}}
* {{Z|en}}: {{W+|en|achievement|guman=erê}}
{{werger-bin}}
s4ao2ya8vzonjgofn7mmd56v4b4cmaj
qesas
0
33161
6334363
6326913
2026-08-17T16:18:38Z
~2026-45017-30
15264
Werger: +îngilîzî : [[killer]]; +îngilîzî : [[murderer]]; +îngilîzî : [[assassin]] (bi alîkarî)
6334363
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku}}
# [[qatil]], [[kujer]], [[qesab]]
#* {{bêmînak|ku}}
# {{kontrol|rêz=erê|[[kujyar]]}}
==== Ji wêjeyê ====
''Peppûkê Kenan Evrenê Arnavût di jîyana xwe de carekê rastîyek got ew jî bû bela serê wî û bû qeşmerê qeşmeran. Kenan Evrenê ku qesasê serê bi hezaran şoreşger û Kurdan e, vê dawîyê tirsa mirinê pêre çêbûbû û xuyaye li kirinên xwe poşman bûye. Lê êdî fêda mirinê ji ecel re tuneye û poşmanîya talî fêde nade kesî.''
(Lotikxane.com, 3/2007)
==== Formên alternatîv ====
* [[qessas]]
==== Jê ====
* [[qesasî]]
* [[qesasîtî]]
* [[qesastî]]
==== Etîmolojî ====
Ji {{ji|ar|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mörder}} {{n}}, {{W+|de|Vergeltung|guman=erê}} {{mê}}
* {{Z|en}}: {{W+|en|killing}}, {{W+|en|retaliate|guman=erê}}, {{W+|en|retaliation|guman=erê}}, {{W+|en|killer}}, {{W+|en|murderer}}, {{W+|en|assassin}}
* {{Z|tr}}: {{W+|tr|fail|guman=erê}}, {{W+|tr|kısas|guman=erê}}
{{werger-bin}}
mz0edla6y77qhh5ms5yp8ti09c19nan
6334368
6334363
2026-08-17T17:36:13Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: sererastkirin
6334368
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku}}
# [[qatil]], [[kujer]], [[qesab]], [[kujyar]]
#* {{jêder-websîte|ku|jêgirtin=Peppûkê Kenan Evrenê Arnavût di jîyana xwe de carekê rastîyek got ew jî bû bela serê wî û bû qeşmerê qeşmeran. Kenan Evrenê ku qesasê serê bi hezaran şoreşger û Kurdan e, vê dawîyê tirsa mirinê pêre çêbûbû û xuyaye li kirinên xwe poşman bûye. Lê êdî fêda mirinê ji ecel re tuneye û poşmanîya talî fêde nade kesî.|websîte=Lotikxane.com|sal=3/2007}}
==== Formên alternatîv ====
* {{g|ku|qessas}}
==== Jê ====
* {{g|ku|qesasî}}
* {{g|ku|qesasîtî}}
* {{g|ku|qesastî}}
==== Etîmolojî ====
Ji {{ji|ar|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mörder}} {{n}}, {{W+|de|Vergeltung|guman=erê}} {{mê}}
* {{Z|en}}: {{W+|en|killing}}, {{W+|en|retaliate|guman=erê}}, {{W+|en|retaliation|guman=erê}}, {{W+|en|killer}}, {{W+|en|murderer}}, {{W+|en|assassin}}
* {{Z|tr}}: {{W+|tr|fail|guman=erê}}, {{W+|tr|kısas|guman=erê}}
{{werger-bin}}
27i1ycb6zdtskk7cy6mu9sk873lbdpq
6334369
6334368
2026-08-17T17:36:36Z
Wikihez
7344
6334369
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku}}
# [[qatil]], [[kujer]], [[qesab]], [[kujyar]]
#* {{jêder-websîte|ku|jêgirtin=Peppûkê Kenan Evrenê Arnavût di jîyana xwe de carekê rastîyek got ew jî bû bela serê wî û bû qeşmerê qeşmeran. Kenan Evrenê ku '''qesasê''' serê bi hezaran şoreşger û Kurdan e, vê dawîyê tirsa mirinê pêre çêbûbû û xuyaye li kirinên xwe poşman bûye. Lê êdî fêda mirinê ji ecel re tuneye û poşmanîya talî fêde nade kesî.|websîte=Lotikxane.com|sal=3/2007}}
==== Formên alternatîv ====
* {{g|ku|qessas}}
==== Jê ====
* {{g|ku|qesasî}}
* {{g|ku|qesasîtî}}
* {{g|ku|qesastî}}
==== Etîmolojî ====
Ji {{ji|ar|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Mörder}} {{n}}, {{W+|de|Vergeltung|guman=erê}} {{mê}}
* {{Z|en}}: {{W+|en|killing}}, {{W+|en|retaliate|guman=erê}}, {{W+|en|retaliation|guman=erê}}, {{W+|en|killer}}, {{W+|en|murderer}}, {{W+|en|assassin}}
* {{Z|tr}}: {{W+|tr|fail|guman=erê}}, {{W+|tr|kısas|guman=erê}}
{{werger-bin}}
ejy3s3o7l8ml7z26cw8ls4l7ekounqr
palas
0
34922
6334465
6313891
2026-08-17T23:40:24Z
Wikihez
7344
6334465
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr 1 ===
{{navdêr|ku|z=m}}
# [[koşk]], [[eywan]], [[seray]], [[qesr]], [[dîwan]], [[koçk]]
#* {{bênimûne|ku}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|en}}: {{W+|en|palace}}, {{W+|en|colonnade|guman=erê}}
* {{Z|tr}}: {{W+|tr|saray}}, {{W+|tr|savan}}, {{W+|tr|palas}}
{{werger-bin}}
=== Navdêr 2 ===
{{navdêr|ku|z=m}}
# {{maneid|ku|merş}} [[merş|Merşek]] e ku ji [[hirî|hiriya]] bizinan hatiye ristin.
#* {{Jêgirtin/Ehmedê Xanî/Mem û Zîn|jêgirtin=Zîn rabû we çûye xelweta xas<br />Tacdîn ne ma ne cil ne '''palas'''<br />Esbab û tecemmul û defîne}}
#* {{J:Ciwanmerd Kulek:Zemanekî li Sê Welatan|jêgirtin={{...}}berêvaran '''palasa''' xwe biavêta ber derî û li ser rûnişta,{{...}}}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Fußmatte|f}}
* {{Z|en}}: {{W+|en|sackcloth}}, {{W+|en|sacking}}
* {{Z|tr}}: {{W+|tr|çul}}
{{werger-bin}}
== {{ziman|tr}} ==
=== Navdêr ===
{{navdêr|tr}}
# palas
#* {{bênimûne|tr}}
# [[hêsan]], [[rihet]]
#* {{bênimûne|tr}}
==== Etîmolojî ====
{{bêetîmolojî|tr}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Palast|m}}, {{W+|de|Schloss|n}}
{{werger-bin}}
1r89na2ijdw6t3bmxxigw41p5l3kd0u
kuncî
0
36357
6334497
6310718
2026-08-18T00:19:32Z
Wikihez
7344
6334497
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA|kuncî}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Sesamum indicum 2.jpg|200px|thumb|Kuncî]]
[[Wêne:Simit-2x.JPG|thumb|200px|Kuncî li ser [[simît]]ekê.]]
{{navdêr|ku|z=m}}, {{k-ya nerm}}
# [[susam|Susam]], [[simsim]], [[zegerek]], <br>[[dar]]ek e li [[germistan]]ên [[Asya]]yê [[şên]] dibe û [[tov]]ek [[hûr]] û [[pehn]] dide ku [[zeyt]] û [[tehîn]] û hin [[xwarin]]ên din jê tên çêkirin.
#* {{J:Mele Xelefê Bafê:Dîroka kelepora mîrnişîniya Botan:2004|jêgirtin=Çandiniyên wan ên avî penbû, '''kuncî''' û maşik in.}}
#* {{jêder|ku|jêgirtin=(...) perîyek nan digere di nava hingiv de radike û li ser '''kuncîyan''' datîne (...)|{{Jêgirtin/Zeynelabidîn Zinar/Xwençe 1/1989}}}}
# [[lib|Lib]]ên [[berhem]]ên wê [[dar]]ê.
#* {{bêmînak|ku}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|کونجی}}
==== Formên alternatîv ====
* [[kuncû]]
==== Navê zanistî ====
* ''[[Sesamum indicum]]''
==== Jê ====
* [[darkuncî]]
==== Etîmolojî ====
[[hevreh|Hevreh]]a soranî {{g|ckb|کونجی}}, farisî {{g|fa|کنجد|tr=koncid, koncod}}, tacikî {{g|tg|кунҷид}}, pehlewî ''(kuncid)'', ji {{ji|ira}}. {{g|hy|քունջութ|tr=kʿunǰutʿ}} ya ermenî, {{g|ka|ქუნჯუტი|tr=k’unǰuti}} ya gurcî, {{g|ru|кунжут}} ya rusî û {{g|tr|küncü}} ya tirkî ji zimanên îranî hatine wergirtin. Lê hevwateya wê di piraniya zimanên ewropî de - bo nimûne {{g|el|sesame}} ya îngilîzî - bi rêya yûnaniya kevn {{g|grc|σήσαμον|tr=sḗsamon}} ji zimanekî samî, ihtimalen aramî {{g|arc|שושמא|tr=şuşma}}, kurtkirina {{g|arc|שומשומא|tr=şumşemma}} ji akadî {{g|akk|𒃻𒈦𒌑𒈬|tr=šamaššammū|w=[[riwek]]a [[zeyt]]ê}} ji {{g|akk|𒉌|w=[[zeyt]]|tr=šaman}} + {{g|akk|𒌑|w=[[riwek]], [[giya]], [[şînkatî]]|tr=šammu}}) hatine wergirtin. [[simsim]] û [[susam]] jî ji wê ne.
==== Werger ====
{{werger-ser|Riwek}}
* {{Z|aa}}: {{W-|aa|simsím|n}}
* {{Z|de}}: {{W+|de|Sesam|n}}
* {{Z|as}}: {{W-|as|তিল}}
* {{Z|bn}}: {{W+|bn|তিল}}
* {{Z|bg}}: {{W-|bg|сусам|n}}
* {{Z|my}}: {{W+|my|နှမ်း}}
* {{Z|bua}}: {{W-|bua|гүнжэд}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|芝麻|tr=zi1 maa4}}
*: {{Z|cmn}}: {{W+|cmn|芝麻|tr=zhīma}}, {{W+|cmn|胡麻|tr=húmá}}
*: {{Z|cdo}}: {{W-|cdo|芝麻|tr=ciĕ-muài}}, {{W-|cdo|油麻|tr=iù-muài}}
*: {{Z|nan}}: {{W-|nan|麻|tr=môa}}, {{W-|nan|油麻|tr=iû-môa}}
* {{Z|da}}: {{W+|da|sesam|g}}
* {{Z|dta}}: {{W-|dta|bal}}, {{W-|dta|cigaa bal}}
* {{Z|id}}: {{W+|id|wijen}}, {{W+|id|bijan}}, {{W+|id|sesam}}
* {{Z|ar}}: {{W-|ar|سِمْسِم|n}}
* {{Z|hy}}: {{W+|hy|շուշմա}}, {{W+|hy|քնջութ|sc=Armn}}
* {{Z|fa}}: {{W+|fa|کنجد|tr=konjed}}
* {{Z|fo}}: {{W-|fo|sesam|nt}}
* {{Z|fi}}: {{W+|fi|seesami}}
* {{Z|fr}}: {{W+|fr|sésame|n}}
* {{Z|gl}}: {{W+|gl|sésamo|n}}
* {{Z|ka}}: {{W-|ka|სეზამი}}, {{W-|ka|ქუნჯუტი}}
* {{Z|hi}}: {{W+|hi|तिल|n}}, {{W+|hi|तील|n}}, {{W+|hi|पवित्र|n}}, {{W+|hi|पितृतर्पण|n}}, {{W+|hi|पापघ्न|n}}, {{W+|hi|पूतधान्य|n}}, {{W+|hi|जटिल|n}}, {{W+|hi|स्नेहफल|n}}, {{W+|hi|तैलफल|n}}, {{W+|hi|हेमधान्य|n}}
* {{Z|nl}}: {{W+|nl|sesam|g}}
* {{Z|he}}: {{W+|he|סומסום|tr=sumsum}}
* {{Z|en}}: {{W+|en|sesame}}
* {{Z|ga}}: {{W-|ga|seasaman|n}}
* {{Z|it}}: {{W+|it|sesamo|n}}
* {{Z|ja}}: {{W+|ja|胡麻|tr=[[ごま]], goma}}
* {{Z|ca}}: {{W-|ca|sèsam|n}}
* {{Z|ko}}: {{W+|ko|참깨}}
* {{Z|la}}: {{W-|la|sēsamum|nt}}
* {{Z|lo}}: {{W-|lo|ງາ}}
* {{Z|mk}}: {{W-|mk|сусам|n}}
* {{Z|ms}}: {{W+|ms|bijan}}, {{W-|ms|lenga}}
* {{Z|mnc}}: {{W-|mnc|ᠮᠠᠯᠠᠩᡤᡡ}}
* {{Z|mi}}: {{W-|mi|hehame}}
* {{Z|hu}}: {{W+|hu|szezám}}
* {{Z|mn}}: {{W+|mn|гүнжид}}
* {{Z|mnw}}: {{W-|mnw|လ္ၚဴ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|sesam|n}}
*: {{Z|nn}}: {{W-|nn|sesam|n}}
* {{Z|pi}}: {{W-|pi|tila|nt}}
* {{Z|pl}}: {{W+|pl|sezam|n}}
* {{Z|pt}}: {{W-|pt|sésamo|n}}, {{W+|pt|gergelim|n}}
* {{Z|pa}}: {{W+|pa|ਤਿਲ|n}}
* {{Z|ro}}: {{W+|ro|susan|n}}, {{W+|ro|sesam|n}}
* {{Z|ru}}: {{W+|ru|сеза́м|n}}, {{W-|ru|сим-си́м|n}}, {{W+|ru|кунжу́т|n}}
* {{Z|sa}}: {{W+|sa|तिल|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|сусам|sc=Cyrl|n}}
*: Latînî: {{W-|sh|susam|sc=Latn|n}}
* {{Z|es}}: {{W+|es|sésamo|n}}
* {{Z|sv}}: {{W-|sv|sesam|g}}
* {{Z|shn}}: {{W-|shn|ငႃး}}
* {{Z|tg}}: {{W+|tg|кунҷид|sc=Cyrl}}
* {{Z|tl}}: {{W-|tl|linga}}
* {{Z|th}}: {{W+|th|งา}}
* {{Z|tdd}}: {{W-|tdd|ᥒᥣᥰ}}
* {{Z|tr}}: {{W+|tr|susam}}
* {{Z|ur}}: {{W-|ur|تل|n|tr=til}}
* {{Z|vi}}: {{W+|vi|mè}}, {{W+|vi|vừng}}
* {{Z|vo}}: {{W-|vo|sesam}}
* {{Z|cy}}: {{W-|cy|sesame|n}}
* {{Z|km}}: {{W+|km|ល្ង}}
* {{Z|yi}}: {{W-|yi|סומסום}}, {{W-|yi|קונזשוט}}
* {{Z|el}}: {{W+|el|σουσαμιά|m}}
*: Kevn: {{W-|grc|σησάμη|m}}
{{werger-bin}}
:{{werger çavkanî|en|sesame|beş=Ev qismê Wergerê|oldid=76168352}}
{{werger-ser|Tovê wê riwekê}}
* {{Z|aa}}: {{W-|aa|simsím|n}}
* {{Z|de}}: {{W+|de|Sesam}}, {{W-|de|Sesamsamen}}
* {{Z|as}}: {{W-|as|তিল}}
* {{Z|az}}: {{W+|az|küncüd}}
* {{Z|bg}}: {{W-|bg|сусам|n}}
* {{Z|my}}: {{W-|my|နှမ်းစေ့}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|芝麻|tr=zi1 maa4}}
*: {{Z|cmn}}: {{W+|cmn|芝麻|tr=zhīmá}}, {{W+|cmn|脂麻|tr=zhīmá}}, {{W+|cmn|胡麻|tr=húmá}}, {{W+|cmn|油麻|tr=yóumá}}
*: {{Z|cdo}}: {{W-|cdo|芝麻|tr=ciĕ-muài}}, {{W-|cdo|油麻|tr=iù-muài}}
*: {{Z|nan}}: {{W-|nan|麻|tr=môa}}, {{W-|nan|油麻|tr=iû-môa}}
* {{Z|da}}: {{W-|da|sesamfrø|nt}}, {{W+|da|sesam|g}}
* {{Z|ar}}: {{W-|ar|سِمْسِم|n}}
* {{Z|hy}}: {{W+|hy|շուշմա|sc=Armn}}, {{W+|hy|քնջութ|sc=Armn}}
* {{Z|fa}}: {{W+|fa|کنجد|tr=konjed}}
* {{Z|fo}}: {{W-|fo|sesamfræ|nt}}
* {{Z|fi}}: {{W+|fi|seesaminsiemen}}
* {{Z|hi}}: {{W+|hi|तिल|n}}, {{W+|hi|तील|n}}, {{W+|hi|पवित्र|n}}, {{W+|hi|पितृतर्पण|n}}, {{W+|hi|पापघ्न|n}}, {{W+|hi|पूतधान्य|n}}, {{W+|hi|जटिल|n}}, {{W+|hi|स्नेहफल|n}}, {{W+|hi|तैलफल|n}}, {{W+|hi|हेमधान्य|n}}
* {{Z|nl}}: {{W+|nl|sesam|g}}, {{W+|nl|sesamzaad|nt}}
* {{Z|he}}: {{W-|he|שמשום}}
* {{Z|en}}: {{W+|en|sesame}}
* {{Z|ja}}: {{W+|ja|胡麻|tr=[[ごま]], goma}}
* {{Z|kn}}: {{W+|kn|ಎಳ್ಳು}}
* {{Z|ca}}: {{W-|ca|sèsam|n}}
* {{Z|ky}}: {{W-|ky|күнжүт}}
* {{Z|ko}}: {{W+|ko|참깨}}
* {{Z|la}}: {{W-|la|sēsamum|nt}}
* {{Z|lo}}: {{W-|lo|ງາ}}
* {{Z|mk}}: {{W-|mk|сусам|n}}
* {{Z|ml}}: {{W+|ml|എള്ള്}}
* {{Z|ms}}: {{W-|ms|lenga}}, {{W+|ms|bijan}}
* {{Z|mnc}}: {{W-|mnc|ᠮᠠᠯᠠᠩᡤᡡ}}
* {{Z|mrw}}: {{W-|mrw|lenga}}
* {{Z|hu}}: {{W+|hu|szezámmag}}
* {{Z|mnw}}: {{W-|mnw|မလ္ၚဴ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|sesam|n}}, {{W-|nb|sesamfrø|nt}}
*: {{Z|nn}}: {{W-|nn|sesam|n}}, {{W-|nn|sesamfrø|nt}}
* {{Z|ug}}: {{W-|ug|كۈنجۈت}}
* {{Z|uz}}: {{W+|uz|kunjut}}
* {{Z|pi}}: {{W-|pi|tila|nt}}
* {{Z|pl}}: {{W+|pl|sezam|n}}
* {{Z|pt}}: {{W-|pt|sésamo|n}}, {{W+|pt|gergelim|n}}
* {{Z|pa}}: {{W+|pa|ਤਿਲ|n}}
* {{Z|kk}}: {{W-|kk|күнжіт}}
* {{Z|ro}}: {{W+|ro|susan|n}}, {{W+|ro|sesam|n}}
* {{Z|ru}}: {{W+|ru|сеза́м|n}}, {{W-|ru|сим-си́м|n}}, {{W+|ru|кунжут|n}}
* {{Z|sa}}: {{W+|sa|तिल|n}}
* {{Z|ceb}}: {{W-|ceb|lunga}}
* {{Z|scn}}: {{W+|scn|ciciulena|m}}, {{W+|scn|giurgiulena|m}}
* {{Z|srn}}: {{W-|srn|abongra}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|сусам|sc=Cyrl|n}}
*: Latînî: {{W-|sh|susam|sc=Latn|n}}
* {{Z|es}}: {{W+|es|ajonjolí|n}}
* {{Z|sv}}: {{W-|sv|sesam|g}}, {{W+|sv|sesamfrö|nt}}
* {{Z|tg}}: {{W+|tg|кунҷид|sc=Cyrl}}
* {{Z|tl}}: {{W-|tl|linga}}
* {{Z|ta}}: {{W+|ta|எள்}}
* {{Z|tsg}}: {{W-|tsg|lunga}}
* {{Z|th}}: {{W+|th|งา}}
* {{Z|te}}: {{W+|te|నువ్వులు|sc=Telu}}
* {{Z|tr}}: {{W+|tr|susam}}
* {{Z|tk}}: {{W-|tk|künji}}
* {{Z|txb}}: {{W-|txb|kuñcit}}
* {{Z|tcy}}: {{W-|tcy|ಎಣ್ಮೆ}}
* {{Z|ur}}: {{W-|ur|تل|n}}
* {{Z|vi}}: {{W+|vi|mè}}, {{W+|vi|vừng}}
* {{Z|vo}}: {{W-|vo|sesamasid}}, {{W-|vo|sesamasids}}
* {{Z|km}}: {{W+|km|ល្ង}}
* {{Z|yi}}: {{W-|yi|סומסום}}, {{W-|yi|קונזשוט}}
* {{Z|el}}: {{W+|el|σουσάμι|nt}}
*: Kevn: {{W-|grc|σήσαμον|nt}}
{{werger-bin}}
:{{werger çavkanî|en|sesame|beş=Ev qismê Wergerê|oldid=76168352}}
{{mijar|ku|riwek}}
9si9mzq9ub2pahc9zp6xqc9wu114za6
6334577
6334497
2026-08-18T02:12:09Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir, --Valahiyên nehewce.)
6334577
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA|kuncî}}
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Sesamum indicum 2.jpg|200px|thumb|Kuncî]]
[[Wêne:Simit-2x.JPG|thumb|200px|Kuncî li ser [[simît]]ekê.]]
{{navdêr|ku|z=m}}, {{k-ya nerm}}
# [[susam|Susam]], [[simsim]], [[zegerek]], <br>[[dar]]ek e li [[germistan]]ên [[Asya]]yê [[şên]] dibe û [[tov]]ek [[hûr]] û [[pehn]] dide ku [[zeyt]] û [[tehîn]] û hin [[xwarin]]ên din jê tên çêkirin.
#* {{J:Mele Xelefê Bafê:Dîroka kelepora mîrnişîniya Botan:2004|jêgirtin=Çandiniyên wan ên avî penbû, '''kuncî''' û maşik in.}}
#* {{jêder|ku|jêgirtin=(...) perîyek nan digere di nava hingiv de radike û li ser '''kuncîyan''' datîne (...)|{{Jêgirtin/Zeynelabidîn Zinar/Xwençe 1/1989}}}}
# [[lib|Libên]] [[berhem]]ên wê [[dar]]ê.
#* {{bêmînak|ku}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|کونجی}}
==== Formên alternatîv ====
* [[kuncû]]
==== Navê zanistî ====
* ''[[Sesamum indicum]]''
==== Jê ====
* [[darkuncî]]
==== Etîmolojî ====
[[hevreh|Hevreha]] soranî {{g|ckb|کونجی}}, farisî {{g|fa|کنجد|tr=koncid, koncod}}, tacikî {{g|tg|кунҷид}}, pehlewî ''(kuncid)'', ji {{ji|ira}}. {{g|hy|քունջութ|tr=kʿunǰutʿ}} ya ermenî, {{g|ka|ქუნჯუტი|tr=k’unǰuti}} ya gurcî, {{g|ru|кунжут}} ya rusî û {{g|tr|küncü}} ya tirkî ji zimanên îranî hatine wergirtin. Lê hevwateya wê di piraniya zimanên ewropî de - bo nimûne {{g|el|sesame}} ya îngilîzî - bi rêya yûnaniya kevn {{g|grc|σήσαμον|tr=sḗsamon}} ji zimanekî samî, ihtimalen aramî {{g|arc|שושמא|tr=şuşma}}, kurtkirina {{g|arc|שומשומא|tr=şumşemma}} ji akadî {{g|akk|𒃻𒈦𒌑𒈬|tr=šamaššammū|w=[[riwek]]a [[zeyt]]ê}} ji {{g|akk|𒉌|w=[[zeyt]]|tr=šaman}} + {{g|akk|𒌑|w=[[riwek]], [[giya]], [[şînkatî]]|tr=šammu}}) hatine wergirtin. [[simsim]] û [[susam]] jî ji wê ne.
==== Werger ====
{{werger-ser|Riwek}}
* {{Z|aa}}: {{W-|aa|simsím|n}}
* {{Z|de}}: {{W+|de|Sesam|n}}
* {{Z|as}}: {{W-|as|তিল}}
* {{Z|bn}}: {{W+|bn|তিল}}
* {{Z|bg}}: {{W-|bg|сусам|n}}
* {{Z|my}}: {{W+|my|နှမ်း}}
* {{Z|bua}}: {{W-|bua|гүнжэд}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|芝麻|tr=zi1 maa4}}
*: {{Z|cmn}}: {{W+|cmn|芝麻|tr=zhīma}}, {{W+|cmn|胡麻|tr=húmá}}
*: {{Z|cdo}}: {{W-|cdo|芝麻|tr=ciĕ-muài}}, {{W-|cdo|油麻|tr=iù-muài}}
*: {{Z|nan}}: {{W-|nan|麻|tr=môa}}, {{W-|nan|油麻|tr=iû-môa}}
* {{Z|da}}: {{W+|da|sesam|g}}
* {{Z|dta}}: {{W-|dta|bal}}, {{W-|dta|cigaa bal}}
* {{Z|id}}: {{W+|id|wijen}}, {{W+|id|bijan}}, {{W+|id|sesam}}
* {{Z|ar}}: {{W-|ar|سِمْسِم|n}}
* {{Z|hy}}: {{W+|hy|շուշմա}}, {{W+|hy|քնջութ|sc=Armn}}
* {{Z|fa}}: {{W+|fa|کنجد|tr=konjed}}
* {{Z|fo}}: {{W-|fo|sesam|nt}}
* {{Z|fi}}: {{W+|fi|seesami}}
* {{Z|fr}}: {{W+|fr|sésame|n}}
* {{Z|gl}}: {{W+|gl|sésamo|n}}
* {{Z|ka}}: {{W-|ka|სეზამი}}, {{W-|ka|ქუნჯუტი}}
* {{Z|hi}}: {{W+|hi|तिल|n}}, {{W+|hi|तील|n}}, {{W+|hi|पवित्र|n}}, {{W+|hi|पितृतर्पण|n}}, {{W+|hi|पापघ्न|n}}, {{W+|hi|पूतधान्य|n}}, {{W+|hi|जटिल|n}}, {{W+|hi|स्नेहफल|n}}, {{W+|hi|तैलफल|n}}, {{W+|hi|हेमधान्य|n}}
* {{Z|nl}}: {{W+|nl|sesam|g}}
* {{Z|he}}: {{W+|he|סומסום|tr=sumsum}}
* {{Z|en}}: {{W+|en|sesame}}
* {{Z|ga}}: {{W-|ga|seasaman|n}}
* {{Z|it}}: {{W+|it|sesamo|n}}
* {{Z|ja}}: {{W+|ja|胡麻|tr=[[ごま]], goma}}
* {{Z|ca}}: {{W-|ca|sèsam|n}}
* {{Z|ko}}: {{W+|ko|참깨}}
* {{Z|la}}: {{W-|la|sēsamum|nt}}
* {{Z|lo}}: {{W-|lo|ງາ}}
* {{Z|mk}}: {{W-|mk|сусам|n}}
* {{Z|ms}}: {{W+|ms|bijan}}, {{W-|ms|lenga}}
* {{Z|mnc}}: {{W-|mnc|ᠮᠠᠯᠠᠩᡤᡡ}}
* {{Z|mi}}: {{W-|mi|hehame}}
* {{Z|hu}}: {{W+|hu|szezám}}
* {{Z|mn}}: {{W+|mn|гүнжид}}
* {{Z|mnw}}: {{W-|mnw|လ္ၚဴ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|sesam|n}}
*: {{Z|nn}}: {{W-|nn|sesam|n}}
* {{Z|pi}}: {{W-|pi|tila|nt}}
* {{Z|pl}}: {{W+|pl|sezam|n}}
* {{Z|pt}}: {{W-|pt|sésamo|n}}, {{W+|pt|gergelim|n}}
* {{Z|pa}}: {{W+|pa|ਤਿਲ|n}}
* {{Z|ro}}: {{W+|ro|susan|n}}, {{W+|ro|sesam|n}}
* {{Z|ru}}: {{W+|ru|сеза́м|n}}, {{W-|ru|сим-си́м|n}}, {{W+|ru|кунжу́т|n}}
* {{Z|sa}}: {{W+|sa|तिल|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|сусам|sc=Cyrl|n}}
*: Latînî: {{W-|sh|susam|sc=Latn|n}}
* {{Z|es}}: {{W+|es|sésamo|n}}
* {{Z|sv}}: {{W-|sv|sesam|g}}
* {{Z|shn}}: {{W-|shn|ငႃး}}
* {{Z|tg}}: {{W+|tg|кунҷид|sc=Cyrl}}
* {{Z|tl}}: {{W-|tl|linga}}
* {{Z|th}}: {{W+|th|งา}}
* {{Z|tdd}}: {{W-|tdd|ᥒᥣᥰ}}
* {{Z|tr}}: {{W+|tr|susam}}
* {{Z|ur}}: {{W-|ur|تل|n|tr=til}}
* {{Z|vi}}: {{W+|vi|mè}}, {{W+|vi|vừng}}
* {{Z|vo}}: {{W-|vo|sesam}}
* {{Z|cy}}: {{W-|cy|sesame|n}}
* {{Z|km}}: {{W+|km|ល្ង}}
* {{Z|yi}}: {{W-|yi|סומסום}}, {{W-|yi|קונזשוט}}
* {{Z|el}}: {{W+|el|σουσαμιά|m}}
*: Kevn: {{W-|grc|σησάμη|m}}
{{werger-bin}}
:{{werger çavkanî|en|sesame|beş=Ev qismê Wergerê|oldid=76168352}}
{{werger-ser|Tovê wê riwekê}}
* {{Z|aa}}: {{W-|aa|simsím|n}}
* {{Z|de}}: {{W+|de|Sesam}}, {{W-|de|Sesamsamen}}
* {{Z|as}}: {{W-|as|তিল}}
* {{Z|az}}: {{W+|az|küncüd}}
* {{Z|bg}}: {{W-|bg|сусам|n}}
* {{Z|my}}: {{W-|my|နှမ်းစေ့}}
* {{Z|zh}}:
*: {{Z|yue}}: {{W-|yue|芝麻|tr=zi1 maa4}}
*: {{Z|cmn}}: {{W+|cmn|芝麻|tr=zhīmá}}, {{W+|cmn|脂麻|tr=zhīmá}}, {{W+|cmn|胡麻|tr=húmá}}, {{W+|cmn|油麻|tr=yóumá}}
*: {{Z|cdo}}: {{W-|cdo|芝麻|tr=ciĕ-muài}}, {{W-|cdo|油麻|tr=iù-muài}}
*: {{Z|nan}}: {{W-|nan|麻|tr=môa}}, {{W-|nan|油麻|tr=iû-môa}}
* {{Z|da}}: {{W-|da|sesamfrø|nt}}, {{W+|da|sesam|g}}
* {{Z|ar}}: {{W-|ar|سِمْسِم|n}}
* {{Z|hy}}: {{W+|hy|շուշմա|sc=Armn}}, {{W+|hy|քնջութ|sc=Armn}}
* {{Z|fa}}: {{W+|fa|کنجد|tr=konjed}}
* {{Z|fo}}: {{W-|fo|sesamfræ|nt}}
* {{Z|fi}}: {{W+|fi|seesaminsiemen}}
* {{Z|hi}}: {{W+|hi|तिल|n}}, {{W+|hi|तील|n}}, {{W+|hi|पवित्र|n}}, {{W+|hi|पितृतर्पण|n}}, {{W+|hi|पापघ्न|n}}, {{W+|hi|पूतधान्य|n}}, {{W+|hi|जटिल|n}}, {{W+|hi|स्नेहफल|n}}, {{W+|hi|तैलफल|n}}, {{W+|hi|हेमधान्य|n}}
* {{Z|nl}}: {{W+|nl|sesam|g}}, {{W+|nl|sesamzaad|nt}}
* {{Z|he}}: {{W-|he|שמשום}}
* {{Z|en}}: {{W+|en|sesame}}
* {{Z|ja}}: {{W+|ja|胡麻|tr=[[ごま]], goma}}
* {{Z|kn}}: {{W+|kn|ಎಳ್ಳು}}
* {{Z|ca}}: {{W-|ca|sèsam|n}}
* {{Z|ky}}: {{W-|ky|күнжүт}}
* {{Z|ko}}: {{W+|ko|참깨}}
* {{Z|la}}: {{W-|la|sēsamum|nt}}
* {{Z|lo}}: {{W-|lo|ງາ}}
* {{Z|mk}}: {{W-|mk|сусам|n}}
* {{Z|ml}}: {{W+|ml|എള്ള്}}
* {{Z|ms}}: {{W-|ms|lenga}}, {{W+|ms|bijan}}
* {{Z|mnc}}: {{W-|mnc|ᠮᠠᠯᠠᠩᡤᡡ}}
* {{Z|mrw}}: {{W-|mrw|lenga}}
* {{Z|hu}}: {{W+|hu|szezámmag}}
* {{Z|mnw}}: {{W-|mnw|မလ္ၚဴ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|sesam|n}}, {{W-|nb|sesamfrø|nt}}
*: {{Z|nn}}: {{W-|nn|sesam|n}}, {{W-|nn|sesamfrø|nt}}
* {{Z|ug}}: {{W-|ug|كۈنجۈت}}
* {{Z|uz}}: {{W+|uz|kunjut}}
* {{Z|pi}}: {{W-|pi|tila|nt}}
* {{Z|pl}}: {{W+|pl|sezam|n}}
* {{Z|pt}}: {{W-|pt|sésamo|n}}, {{W+|pt|gergelim|n}}
* {{Z|pa}}: {{W+|pa|ਤਿਲ|n}}
* {{Z|kk}}: {{W-|kk|күнжіт}}
* {{Z|ro}}: {{W+|ro|susan|n}}, {{W+|ro|sesam|n}}
* {{Z|ru}}: {{W+|ru|сеза́м|n}}, {{W-|ru|сим-си́м|n}}, {{W+|ru|кунжут|n}}
* {{Z|sa}}: {{W+|sa|तिल|n}}
* {{Z|ceb}}: {{W-|ceb|lunga}}
* {{Z|scn}}: {{W+|scn|ciciulena|m}}, {{W+|scn|giurgiulena|m}}
* {{Z|srn}}: {{W-|srn|abongra}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|сусам|sc=Cyrl|n}}
*: Latînî: {{W-|sh|susam|sc=Latn|n}}
* {{Z|es}}: {{W+|es|ajonjolí|n}}
* {{Z|sv}}: {{W-|sv|sesam|g}}, {{W+|sv|sesamfrö|nt}}
* {{Z|tg}}: {{W+|tg|кунҷид|sc=Cyrl}}
* {{Z|tl}}: {{W-|tl|linga}}
* {{Z|ta}}: {{W+|ta|எள்}}
* {{Z|tsg}}: {{W-|tsg|lunga}}
* {{Z|th}}: {{W+|th|งา}}
* {{Z|te}}: {{W+|te|నువ్వులు|sc=Telu}}
* {{Z|tr}}: {{W+|tr|susam}}
* {{Z|tk}}: {{W-|tk|künji}}
* {{Z|txb}}: {{W-|txb|kuñcit}}
* {{Z|tcy}}: {{W-|tcy|ಎಣ್ಮೆ}}
* {{Z|ur}}: {{W-|ur|تل|n}}
* {{Z|vi}}: {{W+|vi|mè}}, {{W+|vi|vừng}}
* {{Z|vo}}: {{W-|vo|sesamasid}}, {{W-|vo|sesamasids}}
* {{Z|km}}: {{W+|km|ល្ង}}
* {{Z|yi}}: {{W-|yi|סומסום}}, {{W-|yi|קונזשוט}}
* {{Z|el}}: {{W+|el|σουσάμι|nt}}
*: Kevn: {{W-|grc|σήσαμον|nt}}
{{werger-bin}}
:{{werger çavkanî|en|sesame|beş=Ev qismê Wergerê|oldid=76168352}}
{{mijar|ku|riwek}}
shaj4ov7k3pizb65wh74olz33iwm229
zên
0
45136
6334576
6324698
2026-08-18T01:20:02Z
InternetArchiveBot
8979
Rescuing 1 sources and tagging 0 as dead.) #IABot (v2.0.9.5
6334576
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{kîte|ku|zên}}
=== Navdêr ===
{{navdêr|ku|z=m}}
# [[bîr]], [[hiş]], [[aqil]]
#* {{bêmînak|ku}}
# [[awir]], [[nerîn]], [[berêxwedan]]
#* {{bêmînak|ku}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Notên bikaranînê ====
* [[zên dan]]
==== Etîmolojî ====
ji erebî {{g|ar|ذهن}}
==== Bide ber ====
* [[zan]]
* [[zen]]
* [[zîn]]
==== Ji Wêjeya Dînî ====
"Hûn weku xelkê vê cîhanê nebin, lê bi nûkirina [[zên]]ê xwe werine guhorrîn, da hûn bizanin ka heza Xwedê ya baş û cihê razîbûnê û bêkêmasî çi ye." (Roma 12:2, bi Badînî [https://web.archive.org/web/20160827223114/http://kitebipiroz.com/behdini/bible/rom/12/2])
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Geist|m}}
* {{Z|en}}: {{W+|en|mind}}
* {{Z|tr}}: {{W+|tr|dimağ}}, {{W+|tr|zihin|guman=erê}}, {{W+|tr|bellem|guman=erê}}
{{werger-bin}}
ekuoi7a9iwtaxtw8jruktsylnoyvj2d
dirik
0
178985
6334313
6305413
2026-08-17T13:07:20Z
Wikihez
7344
[[Special:PermanentLink/6334286]]
6334313
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-IPA}}
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=m}}, {{r-ya req}}
# {{f|ku|dar}} dareke pîjpîjî ye û berên wê tên xwarin
# [[pîj]], [[strî]]
#:mirov di bin siya dirikê de ranakeve, di bin siya spingdarê de radikeve
# û virik galegala vala û bêkêr
# [[tutirk]], [[tudirik]], [[dirî]], [[devî]], [[stirî]], [[gulgenimkirî]], stûnka piştê, [[hajon]], [[kelem]], [[dinî]]
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Formên alternatîv ====
* [[dirrik]]
==== Etîmolojî ====
Ji {{ji|ar|ku|tr=dherî|t=[[strî]]}}
==== Werger ====
{{werger-ser}}
* {{Z|fa}}: {{W+|fa|توت سیاه}}, {{W+|fa|خار کوچک}}, {{W+|fa|سرپناه}}, {{W|fa|خانه و زندگی}}, {{W|fa|سروسامان}}
* {{Z|en}}: {{W|en|bramble}}, {{W|en|spiked}}, {{W|en|spiky}}, {{W|en|spinous}}, {{W|en|spinule}}, {{W|en|spurred}}, {{W|en|thornbush}}, {{W|en|tyne}}, {{W|en|muricated}}, {{W|en|aculeata|guman=erê}}, {{W|en|hispid|guman=erê}}, {{W|en|spinescent|guman=erê}}, {{W|en|spinose|guman=erê}}, {{W|en|couplet|guman=erê}}, {{W|en|thorn-bush|guman=erê}}
* {{Z|tr}}: {{W+|tr|böğürtlen}}, {{W+|tr|bük}}, {{W+|tr|it üzümü}}
{{werger-bin}}
bpdfioikgmgf6wjnqqvc8ilceyanx2h
kwînêr
0
185331
6334426
6310866
2026-08-17T23:04:22Z
Wikihez
7344
6334426
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|z=m}}
# {{rastnivîs|kunêr}}
#* {{J:Öner:2017|jêgirtin='''Kwînêra''' reş di sîngê da hato.|r=85}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
g8zrvmsn4kl2mbuq2vtnx57ah56ko0m
jîndarî
0
263845
6334475
6283123
2026-08-17T23:48:18Z
Wikihez
7344
6334475
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Rengdêr ===
{{rengdêr|ku}}
# jiyan bi ser birin, [[dinyadarî]]<ref>{{Ferhenga Kamêran}}</ref>
#* {{J:Ehmed Huseynî:Bi xewna we pênûsê dilorînim:2002|jêgirtin=Me '''jîndariya''' te bi çeperên matmayî parast.}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W|de|Lebhaftigkeit|f}}, {{W|de|Lebensfrage|guman=erê}}
* {{Z|ar}}: {{W|ar|حيوية}}, {{W|ar|نشاط}}
* {{Z|fa}}: {{W+|fa|چالاکی|guman=erê}}
* {{Z|en}}: {{W+|en|vitality}}, {{W|en|peppiness|guman=erê}}
* {{Z|tr}}: {{W+|tr|hayati}}, {{W+|tr|yaşamsal}}, {{W+|tr|hayatiyet}}, {{W+|tr|canlılık}}
{{werger-bin}}
=== Çavkanî ===
{{çavkanî}}
2cd5n19ndc7k29qf93sco4qgracctdp
monoteîst
0
342488
6334318
6333574
2026-08-17T13:39:38Z
Ghybu
1393
/* Etîmolojî */ [[Bikarhêner:Zerwanda|Zerwanda]]: Tu çito ew dizanî? Kurd zimanê îngilîzî û fransî nizanin? An jî tênê li Tirkiyê kurd hene ? Belkî te di zimanê tirkî hu kiryê...
6334318
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# [[bawermend|Bawermendê]] [[monoteîzm]]ê, doktrîna ku di gerdûnê de yek xwedayî heye.
#: {{hevmane|ku|henîf|tekxwedaperist}}
#* {{jêder-pirtûk|ku|jêgirtin=... '''monoteîst''' in û seranser, bavkanî/patrîarkal in.|url=http://books.google.com.tr/books?id=zALhDwAAQBAJ&pg=PA15&dq=%22monote%C3%AEst%22&hl=&cd=1&source=gbs_api|sernav=KOVARA FELSEFEVAN 1|weşanxane=Na Yayınları|ISBN=9786059017169|rûpel=15}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
Ji {{ji|en|ku|monoteist}} an ji {{ji|fr|ku|monothéiste}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Monotheist|n}}, {{W+|de|Monotheistin|m}}
* {{Z|fi}}: {{W+|fi|monoteisti}}
* {{Z|fr}}: {{W+|fr|monothéiste}}
* {{Z|hi}}: {{W+|hi|एकेश्वरवादी|n}}
* {{Z|en}}: {{W+|en|monotheist}}
* {{Z|ga}}: {{W-|ga|aondiach|n}}
* {{Z|ca}}: {{W-|ca|monoteista|n|m}}
* {{Z|ky}}: {{W-|ky|биркудайчы}}, {{W-|ky|биртеңирчи}}
* {{Z|pl}}: {{W+|pl|monoteista|n}}, {{W-|pl|jednobożca|n}}, {{W-|pl|jednobożnik|n}}
* {{Z|pt}}: {{W+|pt|monoteísta|n|m}}
* {{Z|ru}}: {{W+|ru|монотеи́ст|n|tr=monotɛíst}}, {{W+|ru|монотеи́стка|m|tr=monotɛístka|sc=Cyrl}}
* {{Z|es}}: {{W+|es|monoteísta|n|m}}
* {{Z|tr}}: {{W+|tr|tek tanrıcı}}, {{W+|tr|monoteist}}
{{werger-bin}}
:{{werger çavkanî|en|monotheist|beş=Ev qismê Wergerê|oldid=75697580}}
72ef5tzlwsqkn9ofnszlgwf26tmmvt1
6334319
6334318
2026-08-17T13:46:15Z
Zerwanda
12183
/* Etîmolojî */ [[User:Gyhbu]]
6334319
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# [[bawermend|Bawermendê]] [[monoteîzm]]ê, doktrîna ku di gerdûnê de yek xwedayî heye.
#: {{hevmane|ku|henîf|tekxwedaperist}}
#* {{jêder-pirtûk|ku|jêgirtin=... '''monoteîst''' in û seranser, bavkanî/patrîarkal in.|url=http://books.google.com.tr/books?id=zALhDwAAQBAJ&pg=PA15&dq=%22monote%C3%AEst%22&hl=&cd=1&source=gbs_api|sernav=KOVARA FELSEFEVAN 1|weşanxane=Na Yayınları|ISBN=9786059017169|rûpel=15}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
Ji {{ji|tr|ku|monoteist}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Monotheist|n}}, {{W+|de|Monotheistin|m}}
* {{Z|fi}}: {{W+|fi|monoteisti}}
* {{Z|fr}}: {{W+|fr|monothéiste}}
* {{Z|hi}}: {{W+|hi|एकेश्वरवादी|n}}
* {{Z|en}}: {{W+|en|monotheist}}
* {{Z|ga}}: {{W-|ga|aondiach|n}}
* {{Z|ca}}: {{W-|ca|monoteista|n|m}}
* {{Z|ky}}: {{W-|ky|биркудайчы}}, {{W-|ky|биртеңирчи}}
* {{Z|pl}}: {{W+|pl|monoteista|n}}, {{W-|pl|jednobożca|n}}, {{W-|pl|jednobożnik|n}}
* {{Z|pt}}: {{W+|pt|monoteísta|n|m}}
* {{Z|ru}}: {{W+|ru|монотеи́ст|n|tr=monotɛíst}}, {{W+|ru|монотеи́стка|m|tr=monotɛístka|sc=Cyrl}}
* {{Z|es}}: {{W+|es|monoteísta|n|m}}
* {{Z|tr}}: {{W+|tr|tek tanrıcı}}, {{W+|tr|monoteist}}
{{werger-bin}}
:{{werger çavkanî|en|monotheist|beş=Ev qismê Wergerê|oldid=75697580}}
06l8t27i5rvd1uks12hv0j6jzzeqojt
6334323
6334319
2026-08-17T13:54:28Z
Wikihez
7344
Reverted 1 edit by [[Special:Contributions/Zerwanda|Zerwanda]] ([[User talk:Zerwanda|talk]]): Lazim e di gotûbêjê de izahek binivîsî ([[:m:User:Xiplus/TwinkleGlobal|TwinkleGlobal]])
6334323
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# [[bawermend|Bawermendê]] [[monoteîzm]]ê, doktrîna ku di gerdûnê de yek xwedayî heye.
#: {{hevmane|ku|henîf|tekxwedaperist}}
#* {{jêder-pirtûk|ku|jêgirtin=... '''monoteîst''' in û seranser, bavkanî/patrîarkal in.|url=http://books.google.com.tr/books?id=zALhDwAAQBAJ&pg=PA15&dq=%22monote%C3%AEst%22&hl=&cd=1&source=gbs_api|sernav=KOVARA FELSEFEVAN 1|weşanxane=Na Yayınları|ISBN=9786059017169|rûpel=15}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
Ji {{ji|en|ku|monoteist}} an ji {{ji|fr|ku|monothéiste}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Monotheist|n}}, {{W+|de|Monotheistin|m}}
* {{Z|fi}}: {{W+|fi|monoteisti}}
* {{Z|fr}}: {{W+|fr|monothéiste}}
* {{Z|hi}}: {{W+|hi|एकेश्वरवादी|n}}
* {{Z|en}}: {{W+|en|monotheist}}
* {{Z|ga}}: {{W-|ga|aondiach|n}}
* {{Z|ca}}: {{W-|ca|monoteista|n|m}}
* {{Z|ky}}: {{W-|ky|биркудайчы}}, {{W-|ky|биртеңирчи}}
* {{Z|pl}}: {{W+|pl|monoteista|n}}, {{W-|pl|jednobożca|n}}, {{W-|pl|jednobożnik|n}}
* {{Z|pt}}: {{W+|pt|monoteísta|n|m}}
* {{Z|ru}}: {{W+|ru|монотеи́ст|n|tr=monotɛíst}}, {{W+|ru|монотеи́стка|m|tr=monotɛístka|sc=Cyrl}}
* {{Z|es}}: {{W+|es|monoteísta|n|m}}
* {{Z|tr}}: {{W+|tr|tek tanrıcı}}, {{W+|tr|monoteist}}
{{werger-bin}}
:{{werger çavkanî|en|monotheist|beş=Ev qismê Wergerê|oldid=75697580}}
72ef5tzlwsqkn9ofnszlgwf26tmmvt1
6334345
6334323
2026-08-17T15:20:44Z
Ghybu
1393
/* Etîmolojî */ + tirkî
6334345
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# [[bawermend|Bawermendê]] [[monoteîzm]]ê, doktrîna ku di gerdûnê de yek xwedayî heye.
#: {{hevmane|ku|henîf|tekxwedaperist}}
#* {{jêder-pirtûk|ku|jêgirtin=... '''monoteîst''' in û seranser, bavkanî/patrîarkal in.|url=http://books.google.com.tr/books?id=zALhDwAAQBAJ&pg=PA15&dq=%22monote%C3%AEst%22&hl=&cd=1&source=gbs_api|sernav=KOVARA FELSEFEVAN 1|weşanxane=Na Yayınları|ISBN=9786059017169|rûpel=15}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
Ji {{ji|en|ku|monoteist}} an ji {{ji|fr|ku|monothéiste}} an jî ji {{ji|tr|ku|monoteist}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Monotheist|n}}, {{W+|de|Monotheistin|m}}
* {{Z|fi}}: {{W+|fi|monoteisti}}
* {{Z|fr}}: {{W+|fr|monothéiste}}
* {{Z|hi}}: {{W+|hi|एकेश्वरवादी|n}}
* {{Z|en}}: {{W+|en|monotheist}}
* {{Z|ga}}: {{W-|ga|aondiach|n}}
* {{Z|ca}}: {{W-|ca|monoteista|n|m}}
* {{Z|ky}}: {{W-|ky|биркудайчы}}, {{W-|ky|биртеңирчи}}
* {{Z|pl}}: {{W+|pl|monoteista|n}}, {{W-|pl|jednobożca|n}}, {{W-|pl|jednobożnik|n}}
* {{Z|pt}}: {{W+|pt|monoteísta|n|m}}
* {{Z|ru}}: {{W+|ru|монотеи́ст|n|tr=monotɛíst}}, {{W+|ru|монотеи́стка|m|tr=monotɛístka|sc=Cyrl}}
* {{Z|es}}: {{W+|es|monoteísta|n|m}}
* {{Z|tr}}: {{W+|tr|tek tanrıcı}}, {{W+|tr|monoteist}}
{{werger-bin}}
:{{werger çavkanî|en|monotheist|beş=Ev qismê Wergerê|oldid=75697580}}
bfhjhw13z8yl44e6789bbk21hpe9h45
6334346
6334345
2026-08-17T15:22:24Z
Wikihez
7344
([[m:Special:MyLanguage/User:Jon Harald Søby/diffedit|diffedit]])
6334346
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# [[bawermend|Bawermendê]] [[monoteîzm]]ê, doktrîna ku di gerdûnê de yek xwedayî heye.
#: {{hevmane|ku|henîf|tekxwedaperist}}
#* {{jêder-pirtûk|ku|jêgirtin=... '''monoteîst''' in û seranser, bavkanî/patrîarkal in.|url=http://books.google.com.tr/books?id=zALhDwAAQBAJ&pg=PA15&dq=%22monote%C3%AEst%22&hl=&cd=1&source=gbs_api|sernav=KOVARA FELSEFEVAN 1|weşanxane=Na Yayınları|ISBN=9786059017169|rûpel=15}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
{{navmiletî|ku}}. Bide ber {{ji|en|ku|monoteist}}, {{ji|fr|ku|monothéiste}} an jî ji {{ji|tr|ku|monoteist}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Monotheist|n}}, {{W+|de|Monotheistin|m}}
* {{Z|fi}}: {{W+|fi|monoteisti}}
* {{Z|fr}}: {{W+|fr|monothéiste}}
* {{Z|hi}}: {{W+|hi|एकेश्वरवादी|n}}
* {{Z|en}}: {{W+|en|monotheist}}
* {{Z|ga}}: {{W-|ga|aondiach|n}}
* {{Z|ca}}: {{W-|ca|monoteista|n|m}}
* {{Z|ky}}: {{W-|ky|биркудайчы}}, {{W-|ky|биртеңирчи}}
* {{Z|pl}}: {{W+|pl|monoteista|n}}, {{W-|pl|jednobożca|n}}, {{W-|pl|jednobożnik|n}}
* {{Z|pt}}: {{W+|pt|monoteísta|n|m}}
* {{Z|ru}}: {{W+|ru|монотеи́ст|n|tr=monotɛíst}}, {{W+|ru|монотеи́стка|m|tr=monotɛístka|sc=Cyrl}}
* {{Z|es}}: {{W+|es|monoteísta|n|m}}
* {{Z|tr}}: {{W+|tr|tek tanrıcı}}, {{W+|tr|monoteist}}
{{werger-bin}}
:{{werger çavkanî|en|monotheist|beş=Ev qismê Wergerê|oldid=75697580}}
2f1b8fe3sc7a1inyezja77d1cq1oago
6334347
6334346
2026-08-17T15:22:43Z
Wikihez
7344
([[m:Special:MyLanguage/User:Jon Harald Søby/diffedit|diffedit]])
6334347
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|z=mn}}
# [[bawermend|Bawermendê]] [[monoteîzm]]ê, doktrîna ku di gerdûnê de yek xwedayî heye.
#: {{hevmane|ku|henîf|tekxwedaperist}}
#* {{jêder-pirtûk|ku|jêgirtin=... '''monoteîst''' in û seranser, bavkanî/patrîarkal in.|url=http://books.google.com.tr/books?id=zALhDwAAQBAJ&pg=PA15&dq=%22monote%C3%AEst%22&hl=&cd=1&source=gbs_api|sernav=KOVARA FELSEFEVAN 1|weşanxane=Na Yayınları|ISBN=9786059017169|rûpel=15}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Etîmolojî ====
{{navmiletî|ku}}. Bide ber {{ji|en|ku|monoteist}}, {{ji|fr|ku|monothéiste}} û {{ji|tr|ku|monoteist}}.
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|Monotheist|n}}, {{W+|de|Monotheistin|m}}
* {{Z|fi}}: {{W+|fi|monoteisti}}
* {{Z|fr}}: {{W+|fr|monothéiste}}
* {{Z|hi}}: {{W+|hi|एकेश्वरवादी|n}}
* {{Z|en}}: {{W+|en|monotheist}}
* {{Z|ga}}: {{W-|ga|aondiach|n}}
* {{Z|ca}}: {{W-|ca|monoteista|n|m}}
* {{Z|ky}}: {{W-|ky|биркудайчы}}, {{W-|ky|биртеңирчи}}
* {{Z|pl}}: {{W+|pl|monoteista|n}}, {{W-|pl|jednobożca|n}}, {{W-|pl|jednobożnik|n}}
* {{Z|pt}}: {{W+|pt|monoteísta|n|m}}
* {{Z|ru}}: {{W+|ru|монотеи́ст|n|tr=monotɛíst}}, {{W+|ru|монотеи́стка|m|tr=monotɛístka|sc=Cyrl}}
* {{Z|es}}: {{W+|es|monoteísta|n|m}}
* {{Z|tr}}: {{W+|tr|tek tanrıcı}}, {{W+|tr|monoteist}}
{{werger-bin}}
:{{werger çavkanî|en|monotheist|beş=Ev qismê Wergerê|oldid=75697580}}
qkuxhprbvmci6na32cijvcjnsalz8fi
xwirînî
0
360015
6334431
5019811
2026-08-17T23:08:31Z
Wikihez
7344
6334431
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|z=m}}
#{{rastnivîs|xurînî}}
#* {{J:Albert Camus:Biyanî|jêgirtin=Me '''xwirîniya''' xwe nekir ji ber ku me dixwest em zû têketana avê.}}
#* {{jêder|ku|jêgirtin='''Xwirînî''' di berbangê de ye. Diçin, çend se'etan dixebitin, dûre radiwestin 'taştê' dixwin, ev hîn sibeh e. 4-5 se'etên din, nîvroj, 'firavîn'ê dixwin, êvarî jî 'şîv'ê. Ji ber ku rojên kar ên dirêj in (havînî), zû radikevin, 'paşşîv' tuneye. Zivistanî jî bi pirayî ‘xwirînî’ tuneye, lê ji ber ku şev dirêj in, dereng radikevin, birçî dibin, car caran ‘paşşîv’ê dixwin. Îcar hin herêm ‘xwirînî’ û ‘taştê’ ji dewsa hev bi kar tînin, her du jî ji bo ‘breakfast/qehwaltî). Cîhên ku ‘berdil’ bi kar tînin, wê ji bo bi gelemperî ‘taştê/breakfast/qehwaltî’ bi kar tînin. Hin herêm jî ji dewsa ‘firavîn’ê dibêjin ‘navroj’, an ku nîvê rojê.|Seydo Ahmed di {{kzk}} de, 6/2012}}
kkv6ydihtmxk933i4ampqhjn09j5gk0
Şablon:J:Mela Hisênê Bateyî:Mewlûda Kurdî
10
519174
6334630
6115083
2026-08-18T11:16:32Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mela Hisênê Bateyî/Mewlûda Kurdî]] weke [[Şablon:J:Mela Hisênê Bateyî:Mewlûda Kurdî]] guhart: Bot: Rûpel bar kir
6115083
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku|sal=~1720|nivîskar=[[:Kategorî:Jêgirtinên ji Mela Hesenê Bateyî|Mela Hesenê Bateyî]]|sernav=Mewlûda Kurdî|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}|url={{{url|}}}|jêgirtin={{{jêgirtin|{{{jg|}}}}}}}}<!--
-->{{kategorî|Jêgirtinên ji Mela Hesenê Bateyî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Mela Hesenê Bateyî]]
[[Kategorî:Çavkanî|Mela Hesenê Bateyî]]
</noinclude>
crjt1mnrwoi2egaqhrrd7t6bwyahdvu
Şablon:J:Mem û Zîn:1692
10
519182
6334489
6327758
2026-08-18T00:08:55Z
Wikihez
7344
6334489
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|sal=~1692<!--
-->|nivîskar=[[:Kategorî:Jêgirtinên ji Ehmedê Xanî|Ehmedê Xanî]]<!--
-->|sernav={{#if:{{{sernav|}}}|{{{sernav|}}}|[[w:Mem û Zîn|Mem û Zîn]]}}<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|amadekar={{{amadekar|}}}<!--
-->|tr={{{tr|}}}<!--
-->|weşanxane={{{weşanxane|}}}<!--
-->|sala weşandinê={{{sala weşandinê|}}}<!--
-->|çap={{{çap|}}}<!--
-->|cild={{{cild|}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|isbn={{{isbn|}}}<!--
-->|url={{{url|}}}<!--
-->}}<!--
-->{{kategorî|Jêgirtinên ji Ehmedê Xanî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}</noinclude>
46j60iid770awwrmlrn2wx46bh9dw56
Şablon:J:Melayê Cizîrî:Dîwana Melayê Cizîrî
10
519251
6334490
6230769
2026-08-18T00:14:42Z
Wikihez
7344
6334490
wikitext
text/x-wiki
{{#if:{{{jêgirtin|{{{jg|}}}}}}<!--
-->|{{jêder-pirtûk|ku|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|nivîskar={{{nivîskar|[[:Kategorî:Jêgirtinên ji Melayê Cizîrî|Melayê Cizîrî]]}}}|amadekar={{{amadekar|}}}|sernav={{{sernav|[[w:Dîwan|Dîwan]]}}}|weşanxane={{{weşanxane|}}}|cih={{{cih|}}}|cild={{{cild|}}}|sal={{{sal|~1640}}}|çap={{{çap|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}}}[[Kategorî:Rûpelên bi şaşî û kêmasî]]<!--
-->|{{kitêb|ziman=ku|sal=~1640|nivîskar=[[:Kategorî:Jêgirtinên ji Melayê Cizîrî|Melayê Cizîrî]]|sernav=[[:w:Dîwan|Dîwan]]|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->}}<!--
-->{{kategorî|Jêgirtinên ji Melayê Cizîrî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Melayê Cizîrî]]
[[Kategorî:Çavkanî|Melayê Cizîrî]]
</noinclude>
l6823fu8lelxmx03gtk8bzpq6uhxmj1
6334492
6334490
2026-08-18T00:16:05Z
Wikihez
7344
6334492
wikitext
text/x-wiki
{{#if:{{{jêgirtin|{{{jg|}}}}}}<!--
-->|{{jêder-pirtûk|ku|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|nivîskar={{{nivîskar|[[:Kategorî:Jêgirtinên ji Melayê Cizîrî|Melayê Cizîrî]]}}}|amadekar={{{amadekar|}}}|tr={{{tr|}}}|sernav={{{sernav|[[w:Dîwan|Dîwan]]}}}|weşanxane={{{weşanxane|}}}|cih={{{cih|}}}|cild={{{cild|}}}|sal={{{sal|~1640}}}|çap={{{çap|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}}}[[Kategorî:Rûpelên bi şaşî û kêmasî]]<!--
-->|{{kitêb|ziman=ku|sal=~1640|nivîskar=[[:Kategorî:Jêgirtinên ji Melayê Cizîrî|Melayê Cizîrî]]|sernav=[[:w:Dîwan|Dîwan]]|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->}}<!--
-->{{kategorî|Jêgirtinên ji Melayê Cizîrî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Melayê Cizîrî]]
[[Kategorî:Çavkanî|Melayê Cizîrî]]
</noinclude>
hpwicbwztnrr5yzuurrbqwrpdgk93z6
6334634
6334492
2026-08-18T11:16:36Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Melayê Cizîrî/Dîwana Melayê Cizîrî]] weke [[Şablon:J:Melayê Cizîrî:Dîwana Melayê Cizîrî]] guhart: Bot: Rûpel bar kir
6334492
wikitext
text/x-wiki
{{#if:{{{jêgirtin|{{{jg|}}}}}}<!--
-->|{{jêder-pirtûk|ku|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|nivîskar={{{nivîskar|[[:Kategorî:Jêgirtinên ji Melayê Cizîrî|Melayê Cizîrî]]}}}|amadekar={{{amadekar|}}}|tr={{{tr|}}}|sernav={{{sernav|[[w:Dîwan|Dîwan]]}}}|weşanxane={{{weşanxane|}}}|cih={{{cih|}}}|cild={{{cild|}}}|sal={{{sal|~1640}}}|çap={{{çap|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}}}[[Kategorî:Rûpelên bi şaşî û kêmasî]]<!--
-->|{{kitêb|ziman=ku|sal=~1640|nivîskar=[[:Kategorî:Jêgirtinên ji Melayê Cizîrî|Melayê Cizîrî]]|sernav=[[:w:Dîwan|Dîwan]]|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->}}<!--
-->{{kategorî|Jêgirtinên ji Melayê Cizîrî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Melayê Cizîrî]]
[[Kategorî:Çavkanî|Melayê Cizîrî]]
</noinclude>
hpwicbwztnrr5yzuurrbqwrpdgk93z6
دەستکرن
0
583092
6334402
5791302
2026-08-17T21:21:28Z
Wikihez
7344
/* Mane */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334402
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku}}
# {{ku-Latn|destkirin}}
ojxcz9fsojsn6ak3rzoua6ojrkdlogr
6334403
6334402
2026-08-17T21:24:55Z
Wikihez
7344
Wikihez rûpela [[دهستكرن]] bêyî beralîkirinek bar kir [[دەستکرن]]
6334402
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku}}
# {{ku-Latn|destkirin}}
ojxcz9fsojsn6ak3rzoua6ojrkdlogr
6334404
6334403
2026-08-17T21:25:11Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334404
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|tr=-}}
# {{ku-Latn|destkirin}}
dr57h9pwy6lk38tpfsgab21lpigdter
Şablon:J:Feqiyê Teyran
10
617959
6334505
6114978
2026-08-18T00:23:52Z
Wikihez
7344
6334505
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-kitêb|kitêb}}|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|ku|nivîskar=[[:Kategorî:Jêgirtinên ji Feqiyê Teyran|Feqiyê Teyran]]|amadekar={{{amadekar|}}}|sernav={{{sernav|}}}|sal=~1620|{{{sala weşandinê|{{{sal|}}}}}}|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Feqiyê Teyran}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Feqiyê Teyran]]
{{jgcat|ku}}
</noinclude>
8n08v728c1xea5yyst2919husgletxf
6334506
6334505
2026-08-18T00:24:06Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Feqiyê Teyran]] weke [[Şablon:J:Feqiyê Teyran]] guhart
6334505
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-kitêb|kitêb}}|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|ku|nivîskar=[[:Kategorî:Jêgirtinên ji Feqiyê Teyran|Feqiyê Teyran]]|amadekar={{{amadekar|}}}|sernav={{{sernav|}}}|sal=~1620|{{{sala weşandinê|{{{sal|}}}}}}|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|rûpel={{{rûpel|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Feqiyê Teyran}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Feqiyê Teyran]]
{{jgcat|ku}}
</noinclude>
8n08v728c1xea5yyst2919husgletxf
Şablon:J:Firat Cewerî:Girtî:1986
10
672398
6334508
5883785
2026-08-18T00:24:34Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Girtî]] weke [[Şablon:J:Firat Cewerî:Girtî:1986]] guhart
5883785
wikitext
text/x-wiki
{{pirtûk|nivîskar={{w|Firat Cewerî}}|sernav={{w|Girtî (Pirtûk)|Girtî}}|weşanxane=Weşanên {{w|Nûdem}}|r={{{r|}}}|sal={{w|1986}}|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
[[Kategorî: Çavkanî]]
</noinclude>
mgb2674rga5j35lh3o6tpv7jybarzo6
6334510
6334508
2026-08-18T00:25:40Z
Wikihez
7344
6334510
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Firat Cewerî}}|sernav={{w|Girtî (pirtûk)|Girtî}}|weşanxane=Weşanên {{w|Nûdem}}|sal=1986}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
hn5gglbh4ckbbs6le9ct727bb8o54cm
Şablon:J:Hesenê Metê:Epîlog
10
672401
6334532
5883793
2026-08-18T00:53:10Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334532
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Hesenê Metê|Hesenê Metê]]|sernav=[[w:Epîlog|Epîlog]]|weşanxane=Weşanxaneya [[w:Avesta|Avesta]]}}<!--
-->{{kategorî|Jêgirtinên ji Hesenê Metê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesenê Metê]]
{{jgcat|ku}}
</noinclude>
mnwprq8pw0p7y6uqxy5mtcc9dva2wcx
6334588
6334532
2026-08-18T11:16:01Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesenê Metê/Epîlog]] weke [[Şablon:J:Hesenê Metê:Epîlog]] guhart: Bot: Rûpel bar kir
6334532
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Hesenê Metê|Hesenê Metê]]|sernav=[[w:Epîlog|Epîlog]]|weşanxane=Weşanxaneya [[w:Avesta|Avesta]]}}<!--
-->{{kategorî|Jêgirtinên ji Hesenê Metê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesenê Metê]]
{{jgcat|ku}}
</noinclude>
mnwprq8pw0p7y6uqxy5mtcc9dva2wcx
Şablon:J:Egîdê Xudo:Dê û Dêmarî:1995
10
672410
6334467
5883779
2026-08-17T23:44:12Z
Wikihez
7344
6334467
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}|nivîskar={{w|Egîdê Xudo}}|sal=1986|sernav={{w|Dê û Dêmarî}}|weşanxane=Weşanên {{w|Nûdem}}|sala-weşandinê=1995}}<!--
-->{{kategorî|Jêgirtinên ji Egîdê Xudo}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Egîdê Xudo]]
[[Kategorî: Çavkanî]]
</noinclude>
2bn3w6i5xz6e7r51t9ehjwu6aymcus2
6334468
6334467
2026-08-17T23:44:25Z
Wikihez
7344
6334468
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}|nivîskar={{w|Egîdê Xudo}}|sal=1986|sernav={{w|Dê û Dêmarî}}|weşanxane=Weşanên {{w|Nûdem}}|sala-weşandinê=1995}}<!--
-->{{kategorî|Jêgirtinên ji Egîdê Xudo}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Egîdê Xudo]]
{{refcat|ku}}
</noinclude>
ox76nupt2jb6cvk9inmu10k38vwnd6z
6334469
6334468
2026-08-17T23:44:52Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Egîdê Xudo/Dê û Dêmarî]] weke [[Şablon:J:Egîdê Xudo:Dê û Dêmarî:1995]] guhart
6334468
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}|nivîskar={{w|Egîdê Xudo}}|sal=1986|sernav={{w|Dê û Dêmarî}}|weşanxane=Weşanên {{w|Nûdem}}|sala-weşandinê=1995}}<!--
-->{{kategorî|Jêgirtinên ji Egîdê Xudo}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Egîdê Xudo]]
{{refcat|ku}}
</noinclude>
ox76nupt2jb6cvk9inmu10k38vwnd6z
6334697
6334469
2026-08-18T11:25:19Z
Wikihez
7344
6334697
wikitext
text/x-wiki
f{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}|nivîskar={{w|Egîdê Xudo}}|sal=1986|sernav={{w|Dê û Dêmarî}}|weşanxane=Weşanên {{w|Nûdem}}|sala-weşandinê=1995}}<!--
-->{{kategorî|Jêgirtinên ji Egîdê Xudo}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Egîdê Xudo]]
{{jgcat|ku}}
</noinclude>
jy1q5xd0kqnai7bv4zwljmkp6gxy7tu
6334698
6334697
2026-08-18T11:25:26Z
Wikihez
7344
6334698
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}|nivîskar={{w|Egîdê Xudo}}|sal=1986|sernav={{w|Dê û Dêmarî}}|weşanxane=Weşanên {{w|Nûdem}}|sala-weşandinê=1995}}<!--
-->{{kategorî|Jêgirtinên ji Egîdê Xudo}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Egîdê Xudo]]
{{jgcat|ku}}
</noinclude>
08bes0zp0al6y4jdhiv5oe96gtmdgq2
Şablon:J:Yaşar Kemal:Çiyayê Agirî
10
672411
6334569
5883830
2026-08-18T01:07:15Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334569
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Yaşar Kemal}}|wergêr={{w|Şerefnaz}}|sernav=Çîyayê agirî|weşanxane=Weşanxana {{w|Çanda Kurdî}}|sal=1990|isbn=9789186146313}}<!--
-->{{kategorî|Jêgirtinên ji Şerefnaz}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Yaşar Kemal]]
{{jgcat|ku}}
</noinclude>
ingoa9he49d88k4b37ofkq0ooaaw9rk
6334680
6334569
2026-08-18T11:17:24Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Yaşar Kemal/Çiyayê Agirî]] weke [[Şablon:J:Yaşar Kemal:Çiyayê Agirî]] guhart: Bot: Rûpel bar kir
6334569
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Yaşar Kemal}}|wergêr={{w|Şerefnaz}}|sernav=Çîyayê agirî|weşanxane=Weşanxana {{w|Çanda Kurdî}}|sal=1990|isbn=9789186146313}}<!--
-->{{kategorî|Jêgirtinên ji Şerefnaz}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Yaşar Kemal]]
{{jgcat|ku}}
</noinclude>
ingoa9he49d88k4b37ofkq0ooaaw9rk
Şablon:J:Yaşar Kemal:Dara Hinarê
10
672412
6334566
5883829
2026-08-18T01:06:03Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334566
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Yaşar Kemal}}|wergêr={{w|Firat Cewerî}}|sernav=Dara hinarê|weşanxane={{w|Nûdem}}|sal=1998|isbn=91 8859 2375 1}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Yaşar Kemal]]
[[Kategorî:Çavkanî]]
</noinclude>
eiicfcao0buifbcrymyqkir6owz7dv4
6334567
6334566
2026-08-18T01:06:45Z
Wikihez
7344
6334567
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Yaşar Kemal}}|wergêr={{w|Firat Cewerî}}|sernav=Dara hinarê|weşanxane={{w|Nûdem}}|sal=1998|isbn=91 8859 2375 1}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Yaşar Kemal]]
{{jgcat|ku}}
</noinclude>
q45nfola95wzx32442id2thzntsn8gq
6334678
6334567
2026-08-18T11:17:23Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Yaşar Kemal/Dara Hinarê]] weke [[Şablon:J:Yaşar Kemal:Dara Hinarê]] guhart: Bot: Rûpel bar kir
6334567
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Yaşar Kemal}}|wergêr={{w|Firat Cewerî}}|sernav=Dara hinarê|weşanxane={{w|Nûdem}}|sal=1998|isbn=91 8859 2375 1}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Yaşar Kemal]]
{{jgcat|ku}}
</noinclude>
q45nfola95wzx32442id2thzntsn8gq
Şablon:J:Albert Camus:Biyanî
10
672413
6334428
6330013
2026-08-17T23:06:22Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Albert Camus/Biyanî]] weke [[Şablon:J:Albert Camus:Biyanî]] guhart
6330013
wikitext
text/x-wiki
{{pirtûk|nivîskar=Albert Camus|wergêr={{w|Fawaz Husên}}|sernav={{w|Biyanî}}|orîjînal=L'Etranger|weşanxane={{w|Nûdem}}|cih={{w|Stockholm}}|sal={{w|1995}}|isbn=9188592-138|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Albert Camus]]
[[Kategorî: Çavkanî]]
</noinclude>
ewlff7hfr3z9tjyboahonlm3skz8ac0
6334430
6334428
2026-08-17T23:07:57Z
Wikihez
7344
6334430
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Albert Camus|wergêr={{w|Fawaz Husên}}|sernav={{w|Biyanî}}|orîjînal=lw:fr:L'Etranger|weşanxane={{w|Nûdem}}|cih={{w|Stockholm}}|sal={{w|1995}}|isbn=9188592-138}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Albert Camus]]
[[Kategorî: Çavkanî]]
</noinclude>
o7uk896g6j7m6vygkd8bc94zcu8hnry
6334432
6334430
2026-08-17T23:09:29Z
Wikihez
7344
6334432
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Albert Camus|wergêr={{w|Fawaz Husên}}|sernav={{w|Biyanî}}|orîjînal=lw:fr:L'Etranger|weşanxane={{w|Nûdem}}|cih={{w|Stockholm}}|sal=1995|isbn=9188592-138}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Albert Camus]]
[[Kategorî: Çavkanî]]
</noinclude>
sfil9kp36ag5vf29u835oocnbbt3nzc
6334448
6334432
2026-08-17T23:19:20Z
Wikihez
7344
6334448
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Albert Camus|wergêr={{w|Fawaz Husên}}|sernav={{w|Biyanî}}|orîjînal=lw:fr:L'Etranger|weşanxane={{w|Nûdem}}|cih={{w|Stockholm}}|sal=1995|isbn=9188592-138}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Albert Camus]]
{{jgcat|ku}}
</noinclude>
mfoircflzl1bgqbd0dalvqborma6kd0
Şablon:J:Anton Çexov:Bexçeyê Vîşne
10
672414
6334439
5883776
2026-08-17T23:15:12Z
Wikihez
7344
https://wer-ger.com/bexceye-visne/
6334439
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar={{w|Anton Çexov}}|wergêr={{w|Firat Cewerî}}|sal=1995|isbn=9188592073|sernav=Bexçeyê Vîşne|weşanxane={{w|Nûdem}}|r={{{r|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Anton Çexov]]
{{jgcat|ku}}
</noinclude>
9euuihcv32bridt4kzqnun8ywdjj0rs
6334440
6334439
2026-08-17T23:15:28Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Anton Çexov/Bexçeyê Vîşne]] weke [[Şablon:J:Anton Çexov:Bexçeyê Vîşne]] guhart
6334439
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar={{w|Anton Çexov}}|wergêr={{w|Firat Cewerî}}|sal=1995|isbn=9188592073|sernav=Bexçeyê Vîşne|weşanxane={{w|Nûdem}}|r={{{r|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Anton Çexov]]
{{jgcat|ku}}
</noinclude>
9euuihcv32bridt4kzqnun8ywdjj0rs
Şablon:J:Zeynelabidîn Zinar:Balafira Jînê
10
672415
6334570
5883831
2026-08-18T01:07:24Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334570
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Zeynelabidîn Zinar}}|sernav={{w|Balafira Jînê}}|weşanxane=Weşanên {{w|Pencînar}}|sal=1993|isbn=91 972090 3 1}}<!--
-->{{kategorî|Jêgirtinên ji Zeynelabidîn Zinar}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Zeynelabidîn Zinar]]
{{jgcat|ku}}
</noinclude>
iyv760noofksqrqqqmgajh6gyh056up
6334682
6334570
2026-08-18T11:17:26Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Zeynelabidîn Zinar/Balafira Jînê]] weke [[Şablon:J:Zeynelabidîn Zinar:Balafira Jînê]] guhart: Bot: Rûpel bar kir
6334570
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Zeynelabidîn Zinar}}|sernav={{w|Balafira Jînê}}|weşanxane=Weşanên {{w|Pencînar}}|sal=1993|isbn=91 972090 3 1}}<!--
-->{{kategorî|Jêgirtinên ji Zeynelabidîn Zinar}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Zeynelabidîn Zinar]]
{{jgcat|ku}}
</noinclude>
iyv760noofksqrqqqmgajh6gyh056up
Şablon:J:Frîda Hecî Cewarî:Bîranînên Min
10
672416
6334528
5883790
2026-08-18T00:49:56Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334528
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Frîda Hecî Cewarî}}|sernav=Bîranînên Min|weşanxane=Weşanên {{w|Lîmûş}}|sal=2010}}<!--
-->{{kategorî|Jêgirtinên ji Frîda Hecî Cewarî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Frîda Hecî Cewarî]]
{{jgcat|ku}}
</noinclude>
dovx4rmey1krf2fqoug0t2j4zea94r0
6334580
6334528
2026-08-18T11:15:54Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Frîda Hecî Cewarî/Bîranînên Min]] weke [[Şablon:J:Frîda Hecî Cewarî:Bîranînên Min]] guhart: Bot: Rûpel bar kir
6334528
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Frîda Hecî Cewarî}}|sernav=Bîranînên Min|weşanxane=Weşanên {{w|Lîmûş}}|sal=2010}}<!--
-->{{kategorî|Jêgirtinên ji Frîda Hecî Cewarî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Frîda Hecî Cewarî]]
{{jgcat|ku}}
</noinclude>
dovx4rmey1krf2fqoug0t2j4zea94r0
Şablon:J:Serdar Roşan:Çîroka Hezar û Şevekê
10
672417
6334559
5883824
2026-08-18T01:02:44Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334559
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=|wergêr={{w|Serdar Roşan}}|sernav=Çîrokên Hezar û Şevekê|sal=1998|weşanxane=weşanên ''{{w|Nûdem}}''}}<!--
-->{{kategorî|Jêgirtinên ji Serdar Roşan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Serdar Roşan]]
{{jgcat|ku}}
</noinclude>
8pxwo949d9x0sypxgju2xc8alahcnsv
6334660
6334559
2026-08-18T11:17:11Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Serdar Roşan/Çîroka Hezar û Şevekê]] weke [[Şablon:J:Serdar Roşan:Çîroka Hezar û Şevekê]] guhart: Bot: Rûpel bar kir
6334559
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=|wergêr={{w|Serdar Roşan}}|sernav=Çîrokên Hezar û Şevekê|sal=1998|weşanxane=weşanên ''{{w|Nûdem}}''}}<!--
-->{{kategorî|Jêgirtinên ji Serdar Roşan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Serdar Roşan]]
{{jgcat|ku}}
</noinclude>
8pxwo949d9x0sypxgju2xc8alahcnsv
Şablon:J:K. Mezher Ehmed:Çend Rûpel Ji Dîroka Gelê Kurd
10
672419
6334537
5883798
2026-08-18T00:54:39Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334537
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:K. Mezher Ehmed|K. Mezher Ehmed]]|sernav=[[w:Çend Rûpel Ji Dîroka Gelê Kurd|Çend Rûpel Ji Dîroka Gelê Kurd]]|weşanxane=Weşanên [[w:APEC|APEC]]}}<!--
-->{{kategorî|Jêgirtinên ji K. Mezher Ehmed}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|K. Mezher Ehmed]]
{{jgcat|ku}}
</noinclude>
c8olk2kpdjpvk5beuzbmgwlt3bso0wx
6334598
6334537
2026-08-18T11:16:07Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/K. Mezher Ehmed/Çend Rûpel Ji Dîroka Gelê Kurd]] weke [[Şablon:J:K. Mezher Ehmed:Çend Rûpel Ji Dîroka Gelê Kurd]] guhart: Bot: Rûpel bar kir
6334537
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:K. Mezher Ehmed|K. Mezher Ehmed]]|sernav=[[w:Çend Rûpel Ji Dîroka Gelê Kurd|Çend Rûpel Ji Dîroka Gelê Kurd]]|weşanxane=Weşanên [[w:APEC|APEC]]}}<!--
-->{{kategorî|Jêgirtinên ji K. Mezher Ehmed}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|K. Mezher Ehmed]]
{{jgcat|ku}}
</noinclude>
c8olk2kpdjpvk5beuzbmgwlt3bso0wx
Şablon:J:Xelîl Duhokî:Antolojiya Çîroka Nû ya Kurmancên Başûr
10
672420
6334565
5883828
2026-08-18T01:05:57Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334565
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sernav=[[w:Antolojiya Çîroka Nû ya Kurmancên Başûr|Antolojiya Çîroka Nû ya Kurmancên Başûr]]|nivîskar={{w|Xelîl Duhokî}}|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|cih=Stockholm|sal=1994}}<!--
-->{{kategorî|Jêgirtinên ji Xelîl Duhokî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Xelîl Duhokî]]
</noinclude>
oz9ka6b0rtl4tly84ht36436xky1gux
6334568
6334565
2026-08-18T01:06:56Z
Wikihez
7344
6334568
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sernav=[[w:Antolojiya Çîroka Nû ya Kurmancên Başûr|Antolojiya Çîroka Nû ya Kurmancên Başûr]]|nivîskar={{w|Xelîl Duhokî}}|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|cih=Stockholm|sal=1994}}<!--
-->{{kategorî|Jêgirtinên ji Xelîl Duhokî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Xelîl Duhokî]]
{{jgcat|ku}}
</noinclude>
tgffc328bjkvlenputtotwwmj56nat4
6334676
6334568
2026-08-18T11:17:21Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Xelîl Duhokî/Antolojiya Çîroka Nû ya Kurmancên Başûr]] weke [[Şablon:J:Xelîl Duhokî:Antolojiya Çîroka Nû ya Kurmancên Başûr]] guhart: Bot: Rûpel bar kir
6334568
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sernav=[[w:Antolojiya Çîroka Nû ya Kurmancên Başûr|Antolojiya Çîroka Nû ya Kurmancên Başûr]]|nivîskar={{w|Xelîl Duhokî}}|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|cih=Stockholm|sal=1994}}<!--
-->{{kategorî|Jêgirtinên ji Xelîl Duhokî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Xelîl Duhokî]]
{{jgcat|ku}}
</noinclude>
tgffc328bjkvlenputtotwwmj56nat4
Şablon:J:Antoine de Saint-Exupéry:Mîrzayê Biçûk
10
672421
6334433
5883775
2026-08-17T23:10:13Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Antoine de Saint-Exupéry/Mîrzayê Biçûk]] weke [[Şablon:J:Antoine de Saint-Exupéry:Mîrzayê Biçûk]] guhart
5883775
wikitext
text/x-wiki
{{pirtûk|nivîskar=Antoine de Saint-Exupéry|wergêr={{w|Fawaz Husên}}|sernav={{w|Mîrzayê Biçûk}}|weşanxane=Weşanên {{w|Nûdem}}|cih=Stockholm|sal=1995|rûpel={{{r|}}}|isbn=91-88592-19 7|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Antoine de Saint-Exupéry]]
[[Kategorî: Çavkanî]]
</noinclude>
db1q5ymi03h1du8eou2wwlyzq27hq0k
6334435
6334433
2026-08-17T23:11:36Z
Wikihez
7344
6334435
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Antoine de Saint-Exupéry|wergêr={{w|Fawaz Husên}}|sernav={{w|Mîrzayê Biçûk}}|weşanxane=Weşanên {{w|Nûdem}}|cih=Stockholm|sal=1995|rûpel={{{r|}}}|isbn=91-88592-19 7}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Antoine de Saint-Exupéry]]
{{jgcat|ku}}
</noinclude>
dybgtcmxmkn7u66l884rrwy18xb2twu
Şablon:J:Sidqî Hirorî:Kurê Zinarê Serbilind:1996
10
723350
6334560
5883825
2026-08-18T01:03:02Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334560
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Sidqî Hirorî}}|sernav=Kurê Zinarê Serbilind|weşanxane=Weşanên Nûdem|cih=Stokholm|sal=1996}}<!--
-->{{kategorî|Jêgirtinên ji Sidqî Hirorî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sidqî Hirorî]]
{{jgcat|ku}}
</noinclude>
fbxvzbkdknf4tnpduj0w0hemnu0myn5
6334664
6334560
2026-08-18T11:17:13Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sidqî Hirorî/Kurê Zinarê Serbilind/1996]] weke [[Şablon:J:Sidqî Hirorî:Kurê Zinarê Serbilind:1996]] guhart: Bot: Rûpel bar kir
6334560
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Sidqî Hirorî}}|sernav=Kurê Zinarê Serbilind|weşanxane=Weşanên Nûdem|cih=Stokholm|sal=1996}}<!--
-->{{kategorî|Jêgirtinên ji Sidqî Hirorî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sidqî Hirorî]]
{{jgcat|ku}}
</noinclude>
fbxvzbkdknf4tnpduj0w0hemnu0myn5
Şablon:J:Hesenê Metê:Labîrenta Cinan:1994
10
723351
6334533
5883794
2026-08-18T00:53:33Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334533
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Hesenê Metê}}|sernav={{w|Labîrenta Cinan}}|weşanxane=Weşanên [[w:Welat|Welat]]|sal=1994}}<!--
-->{{kategorî|Jêgirtinên ji Hesenê Metê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesenê Metê]]
{{jgcat|ku}}
</noinclude>
3cyvqb8d9ffpjz6n1zf6ek5vuwimsif
6334590
6334533
2026-08-18T11:16:03Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesenê Metê/Labîrenta Cinan/1994]] weke [[Şablon:J:Hesenê Metê:Labîrenta Cinan:1994]] guhart: Bot: Rûpel bar kir
6334533
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Hesenê Metê}}|sernav={{w|Labîrenta Cinan}}|weşanxane=Weşanên [[w:Welat|Welat]]|sal=1994}}<!--
-->{{kategorî|Jêgirtinên ji Hesenê Metê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesenê Metê]]
{{jgcat|ku}}
</noinclude>
3cyvqb8d9ffpjz6n1zf6ek5vuwimsif
Şablon:rastnivîs/belge
10
793846
6334336
4353071
2026-08-17T14:56:58Z
Wikihez
7344
6334336
wikitext
text/x-wiki
{{demo|sep=dide:<br>|<nowiki>{{rastnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma rastnivîsînê ya kelîmeyekê ji forma nestandard. Ji bo varyantên devokî Şablon:guharto bi kar bînin."
}
</templatedata>
== Binêre ==
* {{ş|şaşî}}
* {{ş|guherto}}
* {{ş|kevnbûyî}}
* {{ş|nadir}}
* {{ş|bnr}}
gdyoaut9cw18x9uuphutcz7kbowhu3r
6334337
6334336
2026-08-17T15:02:16Z
Wikihez
7344
6334337
wikitext
text/x-wiki
{{demo|sep=dide:<br>|<nowiki>{{rastnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma rastnivîsînê ya kelîmeyekê ji forma nestandard. Ji bo varyantên devokî Şablon:guharto bi kar bînin."
}
</templatedata>
== Binêre ==
* {{ş|şaşî}}
* {{ş|guherto}}
* {{ş|kevnbûyî}}
* {{ş|nadir}}
* {{ş|bnr}}
<includeonly>
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
7ejxwo8ajcv9796jd8gtd2wo94fb1nl
6334341
6334337
2026-08-17T15:13:04Z
Wikihez
7344
6334341
wikitext
text/x-wiki
{{demo|sep=dide:<br>|<nowiki>{{rastnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {
"label": "nîşankirina alternatîv"
},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma rastnivîsînê ya kelîmeyekê ji forma nestandard. Ji bo varyantên devokî Şablon:guharto bi kar bînin."
}
</templatedata>
== Binêre ==
* {{ş|şaşî}}
* {{ş|guherto}}
* {{ş|kevnbûyî}}
* {{ş|nadir}}
* {{ş|bnr}}
<includeonly>
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
t9q391q2jzouijvgdr3rtgfr3yplsnb
6334348
6334341
2026-08-17T15:34:34Z
Wikihez
7344
6334348
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
== Bikaranîn ==
* <code><nowiki>{{rastnivîs|koda zimanî|kelîme}}</nowiki></code>
== Nimûne ==
{{demo|sep=dide:<br>|<nowiki>{{rastnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
== TemplateData ====
{{TemplateData header}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {
"label": "nîşankirina alternatîv"
},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma rastnivîsînê ya kelîmeyekê ji forma nestandard. Ji bo varyantên devokî Şablon:guharto bi kar bînin."
}
</templatedata>
== Binêre ==
* {{ş|şaşî}}
* {{ş|guherto}}
* {{ş|kevnbûyî}}
* {{ş|nadir}}
* {{ş|bnr}}
<includeonly>
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
14j8w1pg7o0abiiblcq8ohalhc864yh
6334349
6334348
2026-08-17T15:34:48Z
Wikihez
7344
6334349
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
== Bikaranîn ==
* <code><nowiki>{{rastnivîs|koda zimanî|kelîme}}</nowiki></code>
== Nimûne ==
{{demo|sep=dide:<br>|<nowiki>{{rastnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
== TemplateData ==
{{TemplateData header}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {
"label": "nîşankirina alternatîv"
},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma rastnivîsînê ya kelîmeyekê ji forma nestandard. Ji bo varyantên devokî Şablon:guharto bi kar bînin."
}
</templatedata>
== Binêre ==
* {{ş|şaşî}}
* {{ş|guherto}}
* {{ş|kevnbûyî}}
* {{ş|nadir}}
* {{ş|bnr}}
<includeonly>
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
kl0fq5v4xo2l1kafb6jfdaa9hm1rlbk
Şablon:guherto/belge
10
793848
6334342
4936601
2026-08-17T15:15:16Z
Wikihez
7344
6334342
wikitext
text/x-wiki
{{demo|sep=dide:<br>|<nowiki>{{guharto|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {
"label": "nîşankirina alternatîv"
},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma standard a kelîmeyekê ji forma nestandard. Herwiha binêre Şablon:rastnivîs û Şablon:şaşnivîs"
}
</templatedata>
== Binêre ==
* {{şl|rastnivîs}}
* {{şl|şaşî}}
* {{şl|kevnbûyî}}
* {{şl|bnr}}
* {{şl|awayekî din}}
<includeonly>
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
nxxgtgpu36kl6kx037gdf8yauu7g7h6
Şablon:J:Hesen Qizilcî:Kenê Parsek
10
816395
6334531
5883792
2026-08-18T00:52:16Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334531
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Hesenê Qizilcî|Hesen Qizilcî]]|sernav=[[w:Kenê Parsek|Kenê Parsek]]|weşanxane=[[w:Weşanên Avesta|Weşanên Avesta]]}}<!--
-->{{kategorî|Jêgirtinên ji Hesen Qizilcî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesen Qizilcî]]
{{jgcat|ku}}
</noinclude>
dbfdhj6it3w7l0mps251yoxvhrd3x9v
6334586
6334531
2026-08-18T11:16:00Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesen Qizilcî/Kenê Parsek]] weke [[Şablon:J:Hesen Qizilcî:Kenê Parsek]] guhart: Bot: Rûpel bar kir
6334531
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Hesenê Qizilcî|Hesen Qizilcî]]|sernav=[[w:Kenê Parsek|Kenê Parsek]]|weşanxane=[[w:Weşanên Avesta|Weşanên Avesta]]}}<!--
-->{{kategorî|Jêgirtinên ji Hesen Qizilcî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesen Qizilcî]]
{{jgcat|ku}}
</noinclude>
dbfdhj6it3w7l0mps251yoxvhrd3x9v
Şablon:kevnbûyî/belge
10
826430
6334350
4903736
2026-08-17T15:35:38Z
Wikihez
7344
6334350
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
== Bikaranîn ==
* <code><nowiki>{{kevnbûyî|koda zimanî|peyv}}</nowiki></code>
== Nimûne ==
{{demo|sep=dide:<br>|<nowiki>{{kevnbûyî|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
== TemplateData ====
{{TemplateData header}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {
"label": "nîşankirina alternatîv"
},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma standard a kelîmeyekê ji forma kevnbûyî."
}
</templatedata>
== Binêre ==
* {{şl|rastnivîs}}
* {{şl|şaşî}}
* {{şl|guherto}}
* {{şl|bnr}}
<includeonly>
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
ezdxduqj1aa1aibmt5f8fjuewos7tu6
Şablon:ku-Latn
10
826792
6334361
6233560
2026-08-17T16:11:58Z
Wikihez
7344
6334361
wikitext
text/x-wiki
<i>Bi [[kurdî-erebî|alfabeya erebî]] nivîsina</i> {{nekuarab|{{#invoke:girêdan|g_parametre|kod=ku|peyv={{{1|}}}|face=head}}}}.<includeonly>{{kategorî|Peyvên {{navê zimanî|ku}} bi alfabeya erebî}}</includeonly><noinclude>
{{belgekirin}}
</noinclude>
h40ytykjmgggtlx3fb98a6mgupjg6ox
Şablon:J:Firat Cewerî:Çîroka Malbata Evdo:1999
10
836563
6334527
5883789
2026-08-18T00:42:10Z
Wikihez
7344
6334527
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sernav=[[w:Çîroka Malbata Evdo|Çîroka Malbata Evdo]]|sal=1999|nivîskar=[[w:Firat Cewerî|Firat Cewerî]]|weşanxane=Weşanên [[w:Nûdem|Nûdem]]}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
plpd3vynt9tuuhio3cvqoqop57nythi
6334578
6334527
2026-08-18T11:15:51Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Çîroka Malbata Evdo/1999]] weke [[Şablon:J:Firat Cewerî:Çîroka Malbata Evdo:1999]] guhart: Bot: Rûpel bar kir
6334527
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sernav=[[w:Çîroka Malbata Evdo|Çîroka Malbata Evdo]]|sal=1999|nivîskar=[[w:Firat Cewerî|Firat Cewerî]]|weşanxane=Weşanên [[w:Nûdem|Nûdem]]}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
plpd3vynt9tuuhio3cvqoqop57nythi
Şablon:J:Bavê Nazê:1986
10
836565
6334443
5883777
2026-08-17T23:16:46Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Bavê Nazê/Hêsir û Baran/1986]] weke [[Şablon:J:Bavê Nazê:1986]] guhart
5883777
wikitext
text/x-wiki
{{pirtûk|nivîskar={{w|Bavê Nazê}}|sernav={{w|Hêsir û Baran}}|weşanxane={{w|Jîna Nû}}|sal={{w|1986}}|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Bavê Nazê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Bavê Nazê]]
[[Kategorî: Çavkanî]]
</noinclude>
3mi6rsj6v8ek1jr0xrkr58bes2trr56
6334445
6334443
2026-08-17T23:17:18Z
Wikihez
7344
6334445
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar={{w|Bavê Nazê}}|sernav={{w|Hêsir û Baran}}|weşanxane={{w|Jîna Nû}}|sal=1986}}<!--
-->{{kategorî|Jêgirtinên ji Bavê Nazê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Bavê Nazê]]
{{jgcat|ku}}
</noinclude>
3tc5a66xb0m7bpkcl7ojdwrkzjzs962
6334446
6334445
2026-08-17T23:17:54Z
Wikihez
7344
6334446
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar={{w|Bavê Nazê}}|sernav=Hêsir û Baran|weşanxane={{w|Jîna Nû}}|sal=1986}}<!--
-->{{kategorî|Jêgirtinên ji Bavê Nazê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Bavê Nazê]]
{{jgcat|ku}}
</noinclude>
3xks9gvrtmz2satwhrdvqm8w546y478
Şablon:J:Mehmed Uzun:Hêz û Bedewiya Pênûsê:1993
10
836566
6334544
5883804
2026-08-18T00:58:35Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334544
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mehmed Uzun|Mehmed Uzun]]|sernav=[[w:Hêz û Bedewiya Pênûsê|Hêz û Bedewiya Pênûsê]]|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|sal=1993}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
h1j8tdrlkwcx6ryjjwtyyo9v982txg9
6334614
6334544
2026-08-18T11:16:18Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Hêz û Bedewiya Pênûsê/1993]] weke [[Şablon:J:Mehmed Uzun/Hêz û Bedewiya Pênûsê/1993]] guhart: Bot: Rûpel bar kir
6334544
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mehmed Uzun|Mehmed Uzun]]|sernav=[[w:Hêz û Bedewiya Pênûsê|Hêz û Bedewiya Pênûsê]]|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|sal=1993}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
h1j8tdrlkwcx6ryjjwtyyo9v982txg9
Şablon:J:Mehmed Uzun:Mirina Kalekî Rind:1987
10
836567
6334545
6107694
2026-08-18T00:58:40Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334545
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mehmed Uzun}}|sernav={{w|Mirina Kalekî Rind}}|weşanxane={{w|Weşanxana Orfeus}}|sal=1987}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
hlizc8c6mac9hv5ediycyic3aaktyia
6334616
6334545
2026-08-18T11:16:19Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Mirina Kalekî Rind/1987]] weke [[Şablon:J:Mehmed Uzun/Mirina Kalekî Rind/1987]] guhart: Bot: Rûpel bar kir
6334545
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mehmed Uzun}}|sernav={{w|Mirina Kalekî Rind}}|weşanxane={{w|Weşanxana Orfeus}}|sal=1987}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
hlizc8c6mac9hv5ediycyic3aaktyia
Şablon:J:Firat Cewerî:Kevoka Spî:1996
10
836568
6334512
5883786
2026-08-18T00:28:11Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Kevoka Spî/1996]] weke [[Şablon:J:Firat Cewerî:Kevoka Spî:1996]] guhart
5883786
wikitext
text/x-wiki
{{pirtûk|nivîskar={{w|Firat Cewerî}}|sernav=Kevoka Spî|weşanxane=Nûdem|sal=1996|rûpel={{{r|}}}|isbn=978-9188592156|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
[[Kategorî: Çavkanî]]
</noinclude>
qat5l21sjtaskjuacbfhlm73r4qwnsg
6334514
6334512
2026-08-18T00:30:08Z
Wikihez
7344
6334514
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Firat Cewerî}}|sernav=w:Kevoka Spî|weşanxane=Nûdem|sal=1996|isbn=978-9188592156}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
bvnhom7y3m4u995w75rxuvqp0w3f7gz
Şablon:J:Mele Xelîlê Sêrtî:Nehc-ul Enam:1988
10
836569
6334548
6112547
2026-08-18T01:00:21Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334548
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mele Xelîlê Sêrtî|Mele Xelîlê Sêrtî]]|sernav=[[w:Nehc-ul Enam|Nehc-ul Enam]]|weşanxane=[[w:Weşanxana Kurdistan|Weşanxana Kurdistan]]|sala weşandinê=1988|sal=~1800}}<!--
-->{{kategorî|Jêgirtinên ji Mele Xelîlê Sêrtî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mele Xelîlê Sêrtî]]
{{jgcat|ku}}
</noinclude>
rj02vnbyz13izcipxzcqbt2bk80tmja
6334636
6334548
2026-08-18T11:16:37Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mele Xelîlê Sêrtî/Nehc-ul Enam/1988]] weke [[Şablon:J:Mele Xelîlê Sêrtî:Nehc-ul Enam:1988]] guhart: Bot: Rûpel bar kir
6334548
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mele Xelîlê Sêrtî|Mele Xelîlê Sêrtî]]|sernav=[[w:Nehc-ul Enam|Nehc-ul Enam]]|weşanxane=[[w:Weşanxana Kurdistan|Weşanxana Kurdistan]]|sala weşandinê=1988|sal=~1800}}<!--
-->{{kategorî|Jêgirtinên ji Mele Xelîlê Sêrtî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mele Xelîlê Sêrtî]]
{{jgcat|ku}}
</noinclude>
rj02vnbyz13izcipxzcqbt2bk80tmja
Şablon:J:Fêrîkê Ûsiv:Pampa Min:2009
10
836571
6334529
5883791
2026-08-18T00:50:42Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334529
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Fêrîkê Ûsiv}}|sernav=Pampa Min|weşanxane={{w|Lîmûş}}|cih=Yêrêvan|sal=2009|isbn=978-9939-805-98-6}}<!--
-->{{kategorî|Jêgirtinên ji Fêrîkê Ûsiv}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Fêrîkê Ûsiv]]
{{jgcat|ku}}
</noinclude>
6khigvm8zwjv1qiewss98jqlt33ogw9
6334582
6334529
2026-08-18T11:15:55Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Fêrîkê Ûsiv/Pampa Min/2009]] weke [[Şablon:J:Fêrîkê Ûsiv:Pampa Min:2009]] guhart: Bot: Rûpel bar kir
6334529
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Fêrîkê Ûsiv}}|sernav=Pampa Min|weşanxane={{w|Lîmûş}}|cih=Yêrêvan|sal=2009|isbn=978-9939-805-98-6}}<!--
-->{{kategorî|Jêgirtinên ji Fêrîkê Ûsiv}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Fêrîkê Ûsiv]]
{{jgcat|ku}}
</noinclude>
6khigvm8zwjv1qiewss98jqlt33ogw9
Şablon:J:Hesenê Metê:Smirnoff:1991
10
836572
6334534
6107687
2026-08-18T00:53:43Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334534
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Hesenê Metê|Hesenê Metê]]|sernav=[[w:Smirnoff|Smirnoff]]|weşanxane=[[w:Weşanên Welat|Weşanên Welat]]|sal=1991}}<!--
-->{{kategorî|Jêgirtinên ji Hesenê Metê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesenê Metê]]
{{jgcat|ku}}
</noinclude>
eu1wljmgb7hqnb7iblmt5fca72gfdd4
6334592
6334534
2026-08-18T11:16:04Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesenê Metê/Smirnoff/1991]] weke [[Şablon:J:Hesenê Metê:Smirnoff:1991]] guhart: Bot: Rûpel bar kir
6334534
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Hesenê Metê|Hesenê Metê]]|sernav=[[w:Smirnoff|Smirnoff]]|weşanxane=[[w:Weşanên Welat|Weşanên Welat]]|sal=1991}}<!--
-->{{kategorî|Jêgirtinên ji Hesenê Metê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesenê Metê]]
{{jgcat|ku}}
</noinclude>
eu1wljmgb7hqnb7iblmt5fca72gfdd4
Şablon:J:Sîma Semend:Xezal:1996
10
836582
6334562
5883827
2026-08-18T01:03:15Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334562
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Sîma Semend|Sîma Semend]]|sernav=[[w:Xezal|Xezal]]|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|sal=1996}}<!--
-->{{kategorî|Jêgirtinên ji Silêman Demir}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sîma Semend]]
{{jgcat|ku}}
</noinclude>
p5rqk5vhz9bl1k2cmevq1j4cpqmulxx
6334668
6334562
2026-08-18T11:17:16Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sîma Semend/Xezal/1996]] weke [[Şablon:J:Sîma Semend:Xezal:1996]] guhart: Bot: Rûpel bar kir
6334562
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Sîma Semend|Sîma Semend]]|sernav=[[w:Xezal|Xezal]]|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|sal=1996}}<!--
-->{{kategorî|Jêgirtinên ji Silêman Demir}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sîma Semend]]
{{jgcat|ku}}
</noinclude>
p5rqk5vhz9bl1k2cmevq1j4cpqmulxx
Şablon:J:Hawar:1998:2
10
836588
6334457
6112962
2026-08-17T23:29:05Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998]] weke [[Şablon:Hawar:1998:2]] guhart
6112962
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kovar|ku|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|kovar=[[w:HAWAR|Hawar]]|sernav={{{sernav|}}}|nivîskar={{{nivîskar|}}}|amadekar={{w|Firat Cewerî}}|wergêr={{{terciman|{{{wergêr|}}}}}}|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|cîld=2|hejmar={{{hejmar|{{{hj|24-57}}}}}}|sal={{{sal|1934-1943}}}|sala weşandinê=1998|rûpel={{{r|}}}}}<noinclude>
{{belgekirin|naverok=
{{kurterê|Şablon:Hawar2}}
<templatedata>
{
"params": {
"sernav": {},
"nivîskar": {},
"hejmar": {
"aliases": [
"hj"
]
},
"r": {
"label": "rûpel"
},
"terciman": {
"aliases": [
"wergêr"
],
"label": "terciman",
"description": "terciman"
},
"sal": {
"label": "sal"
}
},
"paramOrder": [
"hejmar",
"sal",
"sernav",
"nivîskar",
"terciman",
"r"
]
}
</templatedata>}}
[[Kategorî:Çavkanî]]
</noinclude>
8dz45p49zd05ybebn9sobdf50zmats1
6334459
6334457
2026-08-17T23:29:20Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Hawar:1998:2]] weke [[Şablon:J:Hawar:1998:2]] guhart
6112962
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kovar|ku|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|kovar=[[w:HAWAR|Hawar]]|sernav={{{sernav|}}}|nivîskar={{{nivîskar|}}}|amadekar={{w|Firat Cewerî}}|wergêr={{{terciman|{{{wergêr|}}}}}}|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|cîld=2|hejmar={{{hejmar|{{{hj|24-57}}}}}}|sal={{{sal|1934-1943}}}|sala weşandinê=1998|rûpel={{{r|}}}}}<noinclude>
{{belgekirin|naverok=
{{kurterê|Şablon:Hawar2}}
<templatedata>
{
"params": {
"sernav": {},
"nivîskar": {},
"hejmar": {
"aliases": [
"hj"
]
},
"r": {
"label": "rûpel"
},
"terciman": {
"aliases": [
"wergêr"
],
"label": "terciman",
"description": "terciman"
},
"sal": {
"label": "sal"
}
},
"paramOrder": [
"hejmar",
"sal",
"sernav",
"nivîskar",
"terciman",
"r"
]
}
</templatedata>}}
[[Kategorî:Çavkanî]]
</noinclude>
8dz45p49zd05ybebn9sobdf50zmats1
Gotûbêja şablonê:J:Sîma Semend:Xezal:1996
11
836589
6334670
3844759
2026-08-18T11:17:17Z
Balyozbot
7867
Balyozbotî/ê navê [[Gotûbêja şablonê:Jêgirtin/Sîma Semend/Xezal/1996]] weke [[Gotûbêja şablonê:J:Sîma Semend:Xezal:1996]] guhart: Bot: Rûpel bar kir
3844759
wikitext
text/x-wiki
{{r|Şêr|Ghybu}} Mirov dikare vê şablonê çawa belge bike? Min lê vekoland lê belê tu mînak di şablonên çavkaniyan de nedît. --[[Bikarhêner:Jiju|Jiju]] ([[Gotûbêja bikarhêner:Jiju|gotûbêj]]) 13:50, 17 kanûna paşîn 2020 (UTC)
c5x8wiq8yarrpddtfuka1f5mo3t3jrm
Şablon:J:Bûbê Eser:Gardiyan:2002
10
836599
6334449
5883778
2026-08-17T23:21:09Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Bûbê Eser/Gardiyan/2002]] weke [[Şablon:J:Bûbê Eser:Gardiyan:2002]] guhart
5883778
wikitext
text/x-wiki
{{pirtûk|nivîskar={{w|Bûbê Eser}}|sernav={{w|Gardiyan}}|weşanxane=Weşanxaneya {{w|Nefel}}|sal={{w|2002}}|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Bûbê Eser}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Bûbê Eser]]
[[Kategorî: Çavkanî]]
</noinclude>
tkcrcavac9a7i0iba6je23en3el4j67
6334451
6334449
2026-08-17T23:23:16Z
Wikihez
7344
https://theraisingofkurds.com/gardiyan-a-bube-eser/
6334451
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar={{w|Bûbê Eser}}|sernav={{w|Gardiyan}}|weşanxane=Weşanxaneya Nefel|r={{{r|}}}|sala-weşandinê=2002|sal=1994}}<!--
-->{{kategorî|Jêgirtinên ji Bûbê Eser}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Bûbê Eser]]
{{jgcat|ku}}
</noinclude>
hz9cpk774h8bngggb2oc6wwefcndcab
6334453
6334451
2026-08-17T23:25:34Z
Wikihez
7344
6334453
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar={{w|Bûbê Eser}}|sernav=Gardiyan|weşanxane=Weşanxaneya Nefel|r={{{r|}}}|sala-weşandinê=2002|sal=1994}}<!--
-->{{kategorî|Jêgirtinên ji Bûbê Eser}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Bûbê Eser]]
{{jgcat|ku}}
</noinclude>
b5ulzsmb530dw1tk8z36e9lepywe1xl
Şablon:J:Mele Xelefê Bafê:Dîroka kelepora mîrnişîniya Botan:2004
10
836601
6334493
5883811
2026-08-18T00:17:01Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Mele Xelefê Bafê/Dîroka kelepora mîrnişîniya Botan/2004]] weke [[Şablon:J:Mele Xelefê Bafê:Dîroka kelepora mîrnişîniya Botan:2004]] guhart
5883811
wikitext
text/x-wiki
{{pirtûk|amadekar=[[w:Mele Xelefê Bafê|Mele Xelefê Bafê]]|sernav=[[w:Dîroka kelepora mîrnişîniya Botan|Dîroka kelepora mîrnişîniya Botan]]|weşanxane=Çapxaneya [[w:Emîral|Emîral]]|sal=2004|rûpel={{{r|}}}|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Mele Xelefê Bafê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mele Xelefê Bafê]]
[[Kategorî: Çavkanî]]
</noinclude>
tculmgyx0jn8irk0q1nmkvadb9cma70
6334496
6334493
2026-08-18T00:18:50Z
Wikihez
7344
6334496
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mele Xelefê Bafê|Mele Xelefê Bafê]]|sernav=[[w:Dîroka kelepora mîrnişîniya Botan|Dîroka kelepora mîrnişîniya Botan]]|weşanxane=Çapxaneya [[w:Emîral|Emîral]]|sal=2004}}<!--
-->{{kategorî|Jêgirtinên ji Mele Xelefê Bafê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mele Xelefê Bafê]]
{{jgcat|ku}}
</noinclude>
8f20awa9bjqqv605s8y3a5utmf9uf4c
6334498
6334496
2026-08-18T00:20:03Z
Wikihez
7344
6334498
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mele Xelefê Bafê|Mele Xelefê Bafê]]|sernav=Dîroka kelepora mîrnişîniya Botan|weşanxane=Çapxaneya [[w:Emîral|Emîral]]|sal=2004}}<!--
-->{{kategorî|Jêgirtinên ji Mele Xelefê Bafê}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mele Xelefê Bafê]]
{{jgcat|ku}}
</noinclude>
cvw1fe95wm8miz1tpbg66lp91e67qnl
Şablon:J:Ehmed Huseynî:Bi xewna we pênûsê dilorînim:2002
10
836602
6334472
5883780
2026-08-17T23:46:49Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ehmed Huseynî/Bi xewna we pênûsê dilorînim/2002]] weke [[Şablon:J:Ehmed Huseynî:Bi xewna we pênûsê dilorînim:2002]] guhart
5883780
wikitext
text/x-wiki
{{pirtûk|nivîskar={{w|Ehmed Huseynî}}|url=http://www.nefel.com/epirtuk/pdf/ehmede_hiseyni_bi_xewna_we_penuse_dilorinim_02.pdf|sernav=Bi xewna we pênûsê dilorînim|weşanxane={{w|Nefel}}|cih={{w|Stockholm}}|sal=2002|rûpel={{{r|}}}|isbn=9197319589|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Ehmed Huseynî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ehmed Huseynî]]
[[Kategorî: Çavkanî]]
</noinclude>
ryd04ab6h9w388k5j9gog5enah9dw7x
6334474
6334472
2026-08-17T23:47:47Z
Wikihez
7344
6334474
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Ehmed Huseynî}}|url=http://www.nefel.com/epirtuk/pdf/ehmede_hiseyni_bi_xewna_we_penuse_dilorinim_02.pdf|sernav=Bi xewna we pênûsê dilorînim|weşanxane={{w|Nefel}}|cih={{w|Stockholm}}|sal=2002|isbn=9197319589}}<!--
-->{{kategorî|Jêgirtinên ji Ehmed Huseynî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ehmed Huseynî]]
{{jgcat|ku}}
</noinclude>
me8akevyf3r5qut6j2n17a950z550hv
Şablon:J:Mahmûd Lewendî:Compûtera ber dilê min:2002
10
836603
6334541
5883800
2026-08-18T00:57:59Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334541
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mahmûd Lewendî|Mahmûd Lewendî]]|sal=2002|sernav=[[w:Compûtera ber dilê min|Compûtera ber dilê min]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]}}<!--
-->{{kategorî|Jêgirtinên ji Mahmûd Lewendî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mahmûd Lewendî]]
{{jgcat|ku}}
</noinclude>
a0dmq2edxw8suk6f8i1a7xsxpx4nusu
6334606
6334541
2026-08-18T11:16:14Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mahmûd Lewendî/Compûtera ber dilê min/2002]] weke [[Şablon:J:Mahmûd Lewendî:Compûtera ber dilê min:2002]] guhart: Bot: Rûpel bar kir
6334541
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mahmûd Lewendî|Mahmûd Lewendî]]|sal=2002|sernav=[[w:Compûtera ber dilê min|Compûtera ber dilê min]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]}}<!--
-->{{kategorî|Jêgirtinên ji Mahmûd Lewendî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mahmûd Lewendî]]
{{jgcat|ku}}
</noinclude>
a0dmq2edxw8suk6f8i1a7xsxpx4nusu
Şablon:J:Mehmed Uzun:Bîra qederê:2002
10
836605
6334542
5883802
2026-08-18T00:58:13Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334542
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mehmed Uzun}}|sernav={{w|Bîra qederê}}|weşanxane=Weşanên {{w|Nefel}}|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
tr1ep40p0e7n23h7i9vt5co4ewp3rxv
6334610
6334542
2026-08-18T11:16:18Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Bîra qederê/2002]] weke [[Şablon:J:Mehmed Uzun/Bîra qederê/2002]] guhart: Bot: Rûpel bar kir
6334542
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mehmed Uzun}}|sernav={{w|Bîra qederê}}|weşanxane=Weşanên {{w|Nefel}}|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
tr1ep40p0e7n23h7i9vt5co4ewp3rxv
6334692
6334610
2026-08-18T11:22:13Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:J:Mehmed Uzun/Bîra qederê/2002]] weke [[Şablon:J:Mehmed Uzun:Bîra qederê:2002]] guhart
6334542
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mehmed Uzun}}|sernav={{w|Bîra qederê}}|weşanxane=Weşanên {{w|Nefel}}|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
tr1ep40p0e7n23h7i9vt5co4ewp3rxv
Şablon:J:Mehmed Uzun:Hawara Dîcleyê 1:2003
10
836606
6334543
5883803
2026-08-18T00:58:25Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334543
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mehmed Uzun}}|sernav={{w|Hawara Dîcleyê 1}}|weşanxane=Weşanên {{w|Nefel}}|sal=2003}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
l2jenrka0zxcst06zn2oofpystv7nmr
6334612
6334543
2026-08-18T11:16:18Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Hawara Dîcleyê 1/2003]] weke [[Şablon:J:Mehmed Uzun/Hawara Dîcleyê 1/2003]] guhart: Bot: Rûpel bar kir
6334543
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mehmed Uzun}}|sernav={{w|Hawara Dîcleyê 1}}|weşanxane=Weşanên {{w|Nefel}}|sal=2003}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
l2jenrka0zxcst06zn2oofpystv7nmr
Şablon:J:Zeynelabidîn Zinar:Xwençe 1:1989
10
836608
6334571
5883832
2026-08-18T01:07:39Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334571
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Zeynelabidîn Zinar}}|sernav={{w|Xwençe 1}}|weşanxane=Weşanxana {{w|Çanda Kurdî}}|sal=1989|isbn=}}<!--
-->{{kategorî|Jêgirtinên ji Zeynelabidîn Zinar}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Zeynelabidîn Zinar]]
{{jgcat|ku}}
</noinclude>
g1u9p5jpt4u3qwjduydhtduerf4fzwo
6334684
6334571
2026-08-18T11:17:27Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Zeynelabidîn Zinar/Xwençe 1/1989]] weke [[Şablon:J:Zeynelabidîn Zinar:Xwençe 1:1989]] guhart: Bot: Rûpel bar kir
6334571
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Zeynelabidîn Zinar}}|sernav={{w|Xwençe 1}}|weşanxane=Weşanxana {{w|Çanda Kurdî}}|sal=1989|isbn=}}<!--
-->{{kategorî|Jêgirtinên ji Zeynelabidîn Zinar}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Zeynelabidîn Zinar]]
{{jgcat|ku}}
</noinclude>
g1u9p5jpt4u3qwjduydhtduerf4fzwo
Şablon:J:M. Elî Kut:Mehkûm:2002
10
836610
6334538
5883799
2026-08-18T00:54:47Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334538
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:M. Elî Kut|M. Elî Kut]]|sernav=[[w:Mehkûm|Mehkûm]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]}}<!--
-->{{kategorî|Jêgirtinên ji M. Elî Kut}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|M. Elî Kut]]
{{jgcat|ku}}
</noinclude>
1k7qlzvnafhdloz4iwt0t0au5wgc8bf
6334600
6334538
2026-08-18T11:16:10Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/M. Elî Kut/Mehkûm/2002]] weke [[Şablon:J:M. Elî Kut:Mehkûm:2002]] guhart: Bot: Rûpel bar kir
6334538
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:M. Elî Kut|M. Elî Kut]]|sernav=[[w:Mehkûm|Mehkûm]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]}}<!--
-->{{kategorî|Jêgirtinên ji M. Elî Kut}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|M. Elî Kut]]
{{jgcat|ku}}
</noinclude>
1k7qlzvnafhdloz4iwt0t0au5wgc8bf
Şablon:J:Mustafa Aydogan:Berê gotin hebû:2002
10
836611
6334549
5883813
2026-08-18T01:00:30Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334549
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mustafa Aydogan|Mustafa Aydogan]]|sernav=[[w:Berê gotin hebû|Berê gotin hebû]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Mustafa Aydogan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mustafa Aydogan]]
{{jgcat|ku}}
</noinclude>
nuhednpv9of5vtfhnlqn7h5a1gqb44z
6334638
6334549
2026-08-18T11:16:39Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mustafa Aydogan/Berê gotin hebû/2002]] weke [[Şablon:J:Mustafa Aydogan:Berê gotin hebû:2002]] guhart: Bot: Rûpel bar kir
6334549
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mustafa Aydogan|Mustafa Aydogan]]|sernav=[[w:Berê gotin hebû|Berê gotin hebû]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Mustafa Aydogan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mustafa Aydogan]]
{{jgcat|ku}}
</noinclude>
nuhednpv9of5vtfhnlqn7h5a1gqb44z
Şablon:J:Perwîz Cihanî:Bilîcan:2002
10
836612
6334551
5883815
2026-08-18T01:00:42Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334551
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Perwîz Cihanî}}|sernav={{w|Bilîcan}}|cîh={{w|Stockholm}}|weşanxane={{w|Nefel}}|sal=2002|isbn=91-89687-12-4}}<!--
-->{{kategorî|Jêgirtinên ji Perwîz Cihanî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
{{jgcat|ku}}
</noinclude>
ed5eazstv1a4fso40n5jf12m4a9fo8j
6334642
6334551
2026-08-18T11:16:47Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Perwîz Cihanî/Bilîcan/2002]] weke [[Şablon:J:Perwîz Cihanî:Bilîcan:2002]] guhart: Bot: Rûpel bar kir
6334551
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Perwîz Cihanî}}|sernav={{w|Bilîcan}}|cîh={{w|Stockholm}}|weşanxane={{w|Nefel}}|sal=2002|isbn=91-89687-12-4}}<!--
-->{{kategorî|Jêgirtinên ji Perwîz Cihanî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
{{jgcat|ku}}
</noinclude>
ed5eazstv1a4fso40n5jf12m4a9fo8j
Şablon:J:Rohat Alakom:Folklor û jinên kurd:2002
10
836613
6334554
5883821
2026-08-18T01:01:03Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334554
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Rohat Alakom|Rohat Alakom]]|sernav=[[w:Folklor û jinên kurd|Folklor û jinên kurd]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Rohat Alakom}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Rohat Alakom]]
{{jgcat|ku}}
</noinclude>
hq2what2huxiu53zg8ybko1b6faj8zc
6334650
6334554
2026-08-18T11:17:04Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Rohat Alakom/Folklor û jinên kurd/2002]] weke [[Şablon:J:Rohat Alakom:Folklor û jinên kurd:2002]] guhart: Bot: Rûpel bar kir
6334554
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Rohat Alakom|Rohat Alakom]]|sernav=[[w:Folklor û jinên kurd|Folklor û jinên kurd]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Rohat Alakom}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Rohat Alakom]]
{{jgcat|ku}}
</noinclude>
hq2what2huxiu53zg8ybko1b6faj8zc
Şablon:J:Salihê Kevirbirî:Filîtê Quto:2002
10
836614
6334557
5883822
2026-08-18T01:02:22Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334557
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Salihê Kevirbirî|Salihê Kevirbirî]]|sernav=[[w:Filîtê Quto|Filîtê Quto]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Salihê Kevirbirî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Salihê Kevirbirî]]
[[Kategorî:Çavkanî]]
</noinclude>
bel8te75wrbefm3yyf66cyziheo6qqc
6334656
6334557
2026-08-18T11:17:08Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Salihê Kevirbirî/Filîtê Quto/2002]] weke [[Şablon:J:Salihê Kevirbirî:Filîtê Quto:2002]] guhart: Bot: Rûpel bar kir
6334557
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Salihê Kevirbirî|Salihê Kevirbirî]]|sernav=[[w:Filîtê Quto|Filîtê Quto]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Salihê Kevirbirî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Salihê Kevirbirî]]
[[Kategorî:Çavkanî]]
</noinclude>
bel8te75wrbefm3yyf66cyziheo6qqc
Şablon:J:Serdar Roşan:Dara gozê:2002
10
836615
6334558
5883823
2026-08-18T01:02:33Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334558
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Serdar Roşan|Serdar Roşan]]|sernav=[[w:Dara gozê|Dara gozê]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Serdar Roşan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Serdar Roşan]]
{{jgcat|ku}}
</noinclude>
gwyqfeuq2kfu13j9zopn9kovdbmtyms
6334658
6334558
2026-08-18T11:17:09Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Serdar Roşan/Dara gozê/2002]] weke [[Şablon:J:Serdar Roşan:Dara gozê:2002]] guhart: Bot: Rûpel bar kir
6334558
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Serdar Roşan|Serdar Roşan]]|sernav=[[w:Dara gozê|Dara gozê]]|weşanxane=Weşanxaneya [[w:Nefel|Nefel]]|sal=2002}}<!--
-->{{kategorî|Jêgirtinên ji Serdar Roşan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Serdar Roşan]]
{{jgcat|ku}}
</noinclude>
gwyqfeuq2kfu13j9zopn9kovdbmtyms
Şablon:J:Mehmed Uzun:Siya Evînê:1989
10
836666
6334546
5883806
2026-08-18T00:58:48Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334546
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mehmed Uzun|Mehmed Uzun]]|sernav=[[w:Siya Evînê|Siya Evînê]]|weşanxane=[[w:Wêşanxana Orfeus|Wêşanxana Orfeus]]|sal=1989}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
[[Kategorî:Çavkanî]]
</noinclude>
lio199w354zkadubr5ob132dfi2anvp
6334618
6334546
2026-08-18T11:16:19Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Siya Evînê/1989]] weke [[Şablon:J:Mehmed Uzun/Siya Evînê/1989]] guhart: Bot: Rûpel bar kir
6334546
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mehmed Uzun|Mehmed Uzun]]|sernav=[[w:Siya Evînê|Siya Evînê]]|weşanxane=[[w:Wêşanxana Orfeus|Wêşanxana Orfeus]]|sal=1989}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
[[Kategorî:Çavkanî]]
</noinclude>
lio199w354zkadubr5ob132dfi2anvp
6334694
6334618
2026-08-18T11:24:16Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:J:Mehmed Uzun/Siya Evînê/1989]] weke [[Şablon:J:Mehmed Uzun:Siya Evînê:1989]] guhart
6334546
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mehmed Uzun|Mehmed Uzun]]|sernav=[[w:Siya Evînê|Siya Evînê]]|weşanxane=[[w:Wêşanxana Orfeus|Wêşanxana Orfeus]]|sal=1989}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
[[Kategorî:Çavkanî]]
</noinclude>
lio199w354zkadubr5ob132dfi2anvp
6334696
6334694
2026-08-18T11:24:30Z
Wikihez
7344
6334696
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Mehmed Uzun|Mehmed Uzun]]|sernav=[[w:Siya Evînê|Siya Evînê]]|weşanxane=[[w:Wêşanxana Orfeus|Wêşanxana Orfeus]]|sal=1989}}<!--
-->{{kategorî|Jêgirtinên ji Mehmed Uzun}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
{{jgcat|ku}}
</noinclude>
tfe8szrc5azty9oq6xlcr6vnij0gm63
Şablon:şaşnivîs/belge
10
836680
6334343
4012678
2026-08-17T15:18:23Z
Wikihez
7344
6334343
wikitext
text/x-wiki
=== Bikaranîn ===
* <code><nowiki>{{şaşî|koda zimanî|peyv}}</nowiki></code>
{{demo|sep=dide:<br>|<nowiki>{{şaşnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
<templatedata>
{
"params": {
"1": {
"label": "koda zimanî",
"suggestedvalues": [
"ku",
"en",
"tr",
"ckb",
"zza",
"sdh",
"hac",
"fr"
]
},
"2": {
"label": "madeya ku were lînkkirin"
},
"cuda": {
"label": "nîşankirina alternatîv"
},
"w": {
"label": "maneya kelîmeyê"
},
"tr": {
"label": "transliterasyon"
},
"sc": {
"label": "koda skrîptê"
}
},
"paramOrder": [
"1",
"2",
"cuda",
"w",
"tr",
"sc"
],
"description": "Ji bo beralîkirina forma rastnivîsînê ya kelîmeyekê ji formeke şaşnivîsî ya berbelav. Ji bo varyantên devokî Şablon:guharto bi kar bînin."
}
</templatedata>
== Binêre ==
* {{şl|rastnivîs}}
* {{şl|guherto}}
* {{şl|bnr}}
* {{şl|kevnbûyî}}
<includeonly>
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
rhvvd0syyw0mpyrr6db4lw7ufr0p1ta
Şablon:J:Fawaz Husên:Siwarên Êşê:1994
10
836834
6334499
5883783
2026-08-18T00:20:34Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Fawaz Husên/Siwarên Êşê/1994]] weke [[Şablon:J:Fawaz Husên:Siwarên Êşê:1994]] guhart
5883783
wikitext
text/x-wiki
{{pirtûk|nivîskar={{w|Fawaz Husên}}|sernav={{w|Siwarên Êşê}}|cîh=|weşanxane={{w|Weşanên Welat}}|sal={{w|1994}}|rûpel={{{r|}}}|isbn=91-97 21 97-0-3}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
[[Kategorî: Çavkanî]]
</noinclude>
ky8sedzv2mpua302udjsz67s3b0srha
6334503
6334499
2026-08-18T00:21:47Z
Wikihez
7344
6334503
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Fawaz Husên}}|sernav=Siwarên Êşê|cîh=|weşanxane=Weşanên Welat|sal=1994|isbn=91-97 21 97-0-3}}<!--
-->{{kategorî|Jêgirtinên ji Fawaz Husên}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
{{jgcat|ku}}
</noinclude>
rfcqui1niomwg68i6cqyewht99lc6zj
Şablon:J:Jan Dost:Mijabad
10
836835
6334536
5883797
2026-08-18T00:54:24Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334536
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Jan Dost}}|sal=[[w:Mijabad|Mijabad]]|sernav=Mijabad|url=https://archive.org/details/Mijabad|weşanxane=[[w:tirej.info|tirej.info]]}}<!--
-->{{kategorî|Jêgirtinên ji Jan Dost}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Jan Dost]]
{{jgcat|ku}}
</noinclude>
q3g6setfs2dtpfz0tx0b9zvkg5hj6po
6334596
6334536
2026-08-18T11:16:06Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Jan Dost/Mijabad]] weke [[Şablon:J:Jan Dost:Mijabad]] guhart: Bot: Rûpel bar kir
6334536
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Jan Dost}}|sal=[[w:Mijabad|Mijabad]]|sernav=Mijabad|url=https://archive.org/details/Mijabad|weşanxane=[[w:tirej.info|tirej.info]]}}<!--
-->{{kategorî|Jêgirtinên ji Jan Dost}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Jan Dost]]
{{jgcat|ku}}
</noinclude>
q3g6setfs2dtpfz0tx0b9zvkg5hj6po
Şablon:J:Melayê Cizirî:Dîwan:2004
10
836836
6334547
6113771
2026-08-18T00:59:14Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334547
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Melayê Cizîrî|Melayê Cizîrî]]|sernav=[[w:Dîwan|Dîwan]]|weşanxane=Weşanxaneya [[w:Nefel (malper)|Nefel]]|sal=~1640|sala weşandinê=2004|amadekar=Arif Zêrevan}}<!--
-->{{kategorî|Jêgirtinên ji Melayê Cizirî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Melayê Cizirî]]
{{jgcat|ku}}
</noinclude>
ru5sa8mgnp7fts4ysc1g7yba96raazl
6334632
6334547
2026-08-18T11:16:34Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Melayê Cizirî/Dîwan/2004]] weke [[Şablon:J:Melayê Cizirî:Dîwan:2004]] guhart: Bot: Rûpel bar kir
6334547
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Melayê Cizîrî|Melayê Cizîrî]]|sernav=[[w:Dîwan|Dîwan]]|weşanxane=Weşanxaneya [[w:Nefel (malper)|Nefel]]|sal=~1640|sala weşandinê=2004|amadekar=Arif Zêrevan}}<!--
-->{{kategorî|Jêgirtinên ji Melayê Cizirî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Melayê Cizirî]]
{{jgcat|ku}}
</noinclude>
ru5sa8mgnp7fts4ysc1g7yba96raazl
Şablon:J:Înan Eroglu:Têbîniyek:2019
10
838151
6334572
5883833
2026-08-18T01:07:54Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334572
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Înan Eroglu}}|sernav=Têbîniyek|weşanxane={{w|Weşanên Avesta}}|sal=2019|isbn=9786052246474}}<!--
-->{{kategorî|Jêgirtinên ji Înan Eroglu}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Înan Eroglu]]
{{jgcat|ku}}
</noinclude>
ozyc6wmdqubm9cttcb1dthomqh39hli
6334686
6334572
2026-08-18T11:17:29Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Înan Eroglu/Têbîniyek/2019]] weke [[Şablon:J:Înan Eroglu:Têbîniyek:2019]] guhart: Bot: Rûpel bar kir
6334572
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Înan Eroglu}}|sernav=Têbîniyek|weşanxane={{w|Weşanên Avesta}}|sal=2019|isbn=9786052246474}}<!--
-->{{kategorî|Jêgirtinên ji Înan Eroglu}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Înan Eroglu]]
{{jgcat|ku}}
</noinclude>
ozyc6wmdqubm9cttcb1dthomqh39hli
Şablon:J:Mîran Janbar:Qolyeya Cecileê
10
838780
6334550
5883814
2026-08-18T01:00:36Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334550
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mîran Janbar}}|sernav={{w|Qolyeya Cécileê}}|weşanxane={{w|Weşanxaneya Lîs}}|sal=2012|çap=1|isbn=9786054497010}}<!--
-->{{kategorî|Jêgirtinên ji Mîran Janbar}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mîran Janbar]]
{{jgcat|ku}}
</noinclude>
lijmobi3n75lfaoh79y6bk42yba9xy4
6334640
6334550
2026-08-18T11:16:45Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mîran Janbar/Qolyeya Cecileê]] weke [[Şablon:J:Mîran Janbar:Qolyeya Cecileê]] guhart: Bot: Rûpel bar kir
6334550
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mîran Janbar}}|sernav={{w|Qolyeya Cécileê}}|weşanxane={{w|Weşanxaneya Lîs}}|sal=2012|çap=1|isbn=9786054497010}}<!--
-->{{kategorî|Jêgirtinên ji Mîran Janbar}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mîran Janbar]]
{{jgcat|ku}}
</noinclude>
lijmobi3n75lfaoh79y6bk42yba9xy4
Şablon:J:Silêman Demir:Sorê Gulê:2002
10
839580
6334561
5883826
2026-08-18T01:03:08Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334561
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Silêman Demir}}|sernav={{w|Sorê Gulê}}|cih={{w|Stockholm}}|weşanxane={{w|Nefel}}|sal=2002|isbn=91-89687-02-7}}<!--
-->{{kategorî|Jêgirtinên ji Silêman Demir}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
{{jgcat|ku}}
</noinclude>
1mhrcqphccbrp7ui04ty55d2tmgmdck
6334666
6334561
2026-08-18T11:17:15Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Silêman Demir/Sorê Gulê/2002]] weke [[Şablon:J:Silêman Demir:Sorê Gulê:2002]] guhart: Bot: Rûpel bar kir
6334561
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Silêman Demir}}|sernav={{w|Sorê Gulê}}|cih={{w|Stockholm}}|weşanxane={{w|Nefel}}|sal=2002|isbn=91-89687-02-7}}<!--
-->{{kategorî|Jêgirtinên ji Silêman Demir}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
{{jgcat|ku}}
</noinclude>
1mhrcqphccbrp7ui04ty55d2tmgmdck
Şablon:J:Ibrahîm Seydo Aydogan:Reş û Spî:2002
10
839581
6334535
5883796
2026-08-18T00:54:01Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334535
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Ibrahîm Seydo Aydogan}}|sernav={{w|Reş û Spî}}|cîh={{w|Stockholm}}|weşanxane={{w|Nefel}}|sal=2002|isbn=91-973195-3-8}}<!--
-->{{kategorî|Jêgirtinên ji Ibrahîm Seydo Aydogan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
{{jgcat|ku}}
</noinclude>
nxxg76con0gueo75x04gy5l8s66e7s1
6334594
6334535
2026-08-18T11:16:05Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Ibrahîm Seydo Aydogan/Reş û Spî/2002]] weke [[Şablon:J:Ibrahîm Seydo Aydogan:Reş û Spî:2002]] guhart: Bot: Rûpel bar kir
6334535
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Ibrahîm Seydo Aydogan}}|sernav={{w|Reş û Spî}}|cîh={{w|Stockholm}}|weşanxane={{w|Nefel}}|sal=2002|isbn=91-973195-3-8}}<!--
-->{{kategorî|Jêgirtinên ji Ibrahîm Seydo Aydogan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Perwîz Cihanî]]
{{jgcat|ku}}
</noinclude>
nxxg76con0gueo75x04gy5l8s66e7s1
Gotûbêja şablonê:J:Fawaz Husên:Siwarên Êşê:1994
11
839617
6334501
3958231
2026-08-18T00:20:35Z
Wikihez
7344
Wikihezî/ê navê [[Gotûbêja şablonê:Jêgirtin/Fawaz Husên/Siwarên Êşê/1994]] weke [[Gotûbêja şablonê:J:Fawaz Husên:Siwarên Êşê:1994]] guhart
3958231
wikitext
text/x-wiki
== Jêder ==
{{ragihandin|Ghybu}} Ji bo pirtûkan em jêder jî lê zêde bikin. Çêtir û hêsantir nebe ji bo me?--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:35, 30 nîsan 2020 (UTC)
:{{r|Balyozxane}}. Em nikarin ji ber ku ev jêgirtinan li bin beşên çavkaniyan jî tên bikaranîn, bo mînak: [https://ku.wiktionary.org/w/index.php?title=x%C3%AEzar&diff=3958222&oldid=3952935]: format cuda ne...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:41, 30 nîsan 2020 (UTC)
::{{ragihandin|Ghybu}} Xebera te ye. Spas!--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 22:49, 30 nîsan 2020 (UTC)
9h79e90ilqpywe68s7qtpjx8q5vd0yc
Şablon:J:Nûbihara Biçûkan
10
844361
6334480
6105890
2026-08-17T23:54:09Z
Wikihez
7344
6334480
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sal=1683|nivîskar=[[:Kategorî:Jêgirtinên ji Ehmedê Xanî|Ehmedê Xanî]]|sernav=[[w:Nûbihara Biçûkan|Nûbihara Biçûkan]]|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|amadekar={{{amadekar|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ehmedê Xanî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Ehmedê Xanî]]
[[Kategorî:Çavkanî|Ehmedê Xanî]]
</noinclude>
rex48squjgxentmcldf8s9qkyjt8o7p
6334481
6334480
2026-08-17T23:54:22Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan]] weke [[Şablon:J:Nûbihara Biçûkan]] guhart
6334480
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sal=1683|nivîskar=[[:Kategorî:Jêgirtinên ji Ehmedê Xanî|Ehmedê Xanî]]|sernav=[[w:Nûbihara Biçûkan|Nûbihara Biçûkan]]|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|amadekar={{{amadekar|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ehmedê Xanî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Ehmedê Xanî]]
[[Kategorî:Çavkanî|Ehmedê Xanî]]
</noinclude>
rex48squjgxentmcldf8s9qkyjt8o7p
6334485
6334481
2026-08-17T23:54:37Z
Wikihez
7344
6334485
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sal=1683|nivîskar=[[:Kategorî:Jêgirtinên ji Ehmedê Xanî|Ehmedê Xanî]]|sernav=[[w:Nûbihara Biçûkan|Nûbihara Biçûkan]]|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|amadekar={{{amadekar|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ehmedê Xanî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Ehmedê Xanî]]
{{jgcat|ku}}
</noinclude>
5f9cedtvalx42j5wskvoq6yk59zzn2m
6334487
6334485
2026-08-17T23:56:56Z
Wikihez
7344
6334487
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|tr={{{tr|}}}<!--
-->|sal=1683|nivîskar=[[:Kategorî:Jêgirtinên ji Ehmedê Xanî|Ehmedê Xanî]]|sernav=[[w:Nûbihara Biçûkan|Nûbihara Biçûkan]]|weşanxane={{{weşanxane|}}}|çap={{{çap|}}}|cild={{{cild|}}}|amadekar={{{amadekar|}}}|isbn={{{isbn|}}}|url={{{url|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ehmedê Xanî}}<!--
-->{{kategorî|Ji wêjeya klasîk ({{navê zimanî|ku}})}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Ehmedê Xanî]]
{{jgcat|ku}}
</noinclude>
4lip81icj6hcazy5idtdgfepfph27sm
Gotûbêja şablonê:J:Nûbihara Biçûkan
11
844364
6334483
3990675
2026-08-17T23:54:23Z
Wikihez
7344
Wikihezî/ê navê [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan]] weke [[Gotûbêja şablonê:J:Nûbihara Biçûkan]] guhart
3990675
wikitext
text/x-wiki
{{r|Balyozxane}} Te di navê vê rûpelêde "Şablon:" du çara nivîsandîye. -- [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 14:00, 20 hezîran 2020 (UTC)
:{{r|Guherto}} Gelek spas! :D --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 14:03, 20 hezîran 2020 (UTC)
pjgf1c9e3rru88h8x179q8pzaootdya
Gotûbêja şablonê:J:Serdar Roşan:Çîroka Hezar û Şevekê
11
844385
6334662
3993048
2026-08-18T11:17:12Z
Balyozbot
7867
Balyozbotî/ê navê [[Gotûbêja şablonê:Jêgirtin/Serdar Roşan/Çîroka Hezar û Şevekê]] weke [[Gotûbêja şablonê:J:Serdar Roşan:Çîroka Hezar û Şevekê]] guhart: Bot: Rûpel bar kir
3993048
wikitext
text/x-wiki
Wergerandina pirtûka ''{{w|Hezar û yek şev}}'' e?--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:03, 21 hezîran 2020 (UTC)
:{{r|Ghybu}} Erê lê nav çewt e. bnr [https://www.kurdipedia.org/files/books/2014/76470.PDF?ver=130454159999777568 1] --[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 18:10, 21 hezîran 2020 (UTC)
::Pirtûkeke bi zimanê erebî ye feqet ji zimanê swêdî hatiye wergerandin...--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:15, 21 hezîran 2020 (UTC)
9hpunmihuzpc5l92oes5nai42noqjqq
Şablon:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye
10
845861
6334699
6113739
2026-08-18T11:25:48Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye]] weke [[Şablon:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye]] guhart
6113739
wikitext
text/x-wiki
<includeonly>{{pirtûk|nivîskar=Mele Mehmûdê Bayezîdî|amadekar=Jan Dost|sernav={{w|Adetên kurdan (pirtûk)|Adat û Rusûmatnameê Ekradiye}}|weşanxane={{w|Nûbihar (weşanxane)|Weşanxaneya Nûbihar}}|sal=~1858|sala weşandinê=2010|rûpel={{{r|}}}|isbn=9789944360678|ziman=ku}}[[Kategorî:Ji wêjeya klasîk ({{navê zimanî|ku}})]]</includeonly><noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mele Mehmûdê Bayezîdî]]
[[Kategorî: Çavkanî]]
</noinclude>
jwvi4ua0vux2nsx92jvd3pjks0tocnj
Gotûbêja şablonê:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye
11
845862
6334701
5021767
2026-08-18T11:25:49Z
Wikihez
7344
Wikihezî/ê navê [[Gotûbêja şablonê:jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye]] weke [[Gotûbêja şablonê:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye]] guhart
5021767
wikitext
text/x-wiki
{{silav|Rewiye tine}}. Vê şablonê bi şablona jêder an tenê serê xwe dikarî bi kar bînî. Mînak:
{{jêder|ku|{{jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye|r=?}}}}
an
{{jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye|r=?}}
Qewet be--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 00:40, 1 tebax 2020 (UTC)
86iiees9ey5ailgnigom65c3vgs6xso
birqetav
0
873246
6334410
5020793
2026-08-17T21:58:42Z
~2026-38561-13
15112
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +tr:navdêr:
6334410
wikitext
text/x-wiki
{{binêre herwiha|bilqetav}}
* {{jêder-pirtûk|ku|jêgirtin=Îcarê dibêjin ku di vê şeva tarî û '''birqetav'''ê, her çi kes curat biketin û siwar bibe û biçite Başxanê...|nivîskar=Mele Mehmûdê Bazîdî|sernav=Cami’eya Riseleyan û Hikayetan Bi Zimanê Kurmancî|cîh=Stembol|weşanxane={{w|Weşanên Lîs|Lîs}}|sal=2010|çap=1|rûpel=85|isbn=978-605-5683-27-6}}
== {{ziman|tr}} ==
=== Bilêvkirin ===
Yıldırım
=== Navdêr ===
{{navdêr|tr|c=y}}
# {{mane?|tr}}
#* {{nimûne?|tr}}
==== Etîmolojî ====
{{ji?|tr}}
pd0bv4f2oru08dtkhe1x2k7ayyp09ld
6334411
6334410
2026-08-17T22:12:14Z
Wikihez
7344
Guhartinên [[Special:Contributions/~2026-38561-13|~2026-38561-13]] ([[User talk:~2026-38561-13|gotûbêj]]) hatine betalkirin, vegerand guhartoya dawî ya [[User:Balyozbot|Balyozbot]]
5020793
wikitext
text/x-wiki
{{binêre herwiha|bilqetav}}
* {{jêder-pirtûk|ku|jêgirtin=Îcarê dibêjin ku di vê şeva tarî û '''birqetav'''ê, her çi kes curat biketin û siwar bibe û biçite Başxanê...|nivîskar=Mele Mehmûdê Bazîdî|sernav=Cami’eya Riseleyan û Hikayetan Bi Zimanê Kurmancî|cîh=Stembol|weşanxane={{w|Weşanên Lîs|Lîs}}|sal=2010|çap=1|rûpel=85|isbn=978-605-5683-27-6}}
0hm7k4nw7gwyw5q0lx7prt6p7hozi9z
6334412
6334411
2026-08-17T22:17:01Z
Wikihez
7344
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +ku:navdêr:{{guharto|ku|bilqetav}}
6334412
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=m}}
# {{guharto|ku|bilqetav}}
#* {{jêder-pirtûk|ku|jêgirtin=Îcarê dibêjin ku di vê şeva tarî û '''birqetav'''ê, her çi kes curat biketin û siwar bibe û biçite Başxanê...|nivîskar=w:Mele Mehmûdê Bazîdî|amadekar={{w|Ziya Avcı|Z[iya] Avcı}}|sernav=Cami’eya Riseleyan û Hikayetan Bi Zimanê Kurmancî|cîh=w:Stembol|weşanxane={{w|Weşanên Lîs|Lîs}}|sal=~1860|sala-weşandinê=2010|çap=1|rûpel=85|isbn=978-605-5683-27-6}}
==== Tewandin ====
{{ku-tewîn-nav|mê}}
ghqh175n6kvahd6twpqpjykjbpvqszr
Şablon:Ç:M. Malmîsanij:Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû
10
873500
6334539
4266740
2026-08-18T00:57:00Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334539
wikitext
text/x-wiki
({{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mistefa Aydogan}}; {{w|Zana Farqînî}}; {{w|Newzad Hirorî}}; {{w|Huseyin Kartal}}; {{w|Remzî Kerîm}}; {{w|Mahmûd Lewendî}}; {{w|M. Emîn Narozî}}; {{w|Reşo Zîlan}}|amadekar= {{w|Malmîsanij|M. Malmîsanij}})|sernav=Ji bo rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya nû|weşanxane=Weşanên Rûpel|cih=Stembol|sal=2018|çap=2|isbn=978-605-66985-2-1}}<noinclude>{{belgekirin}}[[Kategorî:Çavkanî|ferheng]]</noinclude>
7nnnq4q4izlwa4hmi9icigbyuxg0k5o
6334602
6334539
2026-08-18T11:16:11Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/M. Malmîsanij/Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû]] weke [[Şablon:J:M. Malmîsanij:Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû]] guhart: Bot: Rûpel bar kir
6334539
wikitext
text/x-wiki
({{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Mistefa Aydogan}}; {{w|Zana Farqînî}}; {{w|Newzad Hirorî}}; {{w|Huseyin Kartal}}; {{w|Remzî Kerîm}}; {{w|Mahmûd Lewendî}}; {{w|M. Emîn Narozî}}; {{w|Reşo Zîlan}}|amadekar= {{w|Malmîsanij|M. Malmîsanij}})|sernav=Ji bo rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya nû|weşanxane=Weşanên Rûpel|cih=Stembol|sal=2018|çap=2|isbn=978-605-66985-2-1}}<noinclude>{{belgekirin}}[[Kategorî:Çavkanî|ferheng]]</noinclude>
7nnnq4q4izlwa4hmi9icigbyuxg0k5o
Şablon:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983
10
933266
6334552
5883819
2026-08-18T01:00:48Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334552
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Qanatê Kurdo}}|sernav={{w|Tarîxa Edebyeta Kurdî}}|weşanxane={{w|Roja Nû}}|sal=1983}}<!--
-->{{kategorî|Jêgirtinên ji Qanatê Kurdo}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran (kurmancî)|Qanatê Kurdo]]
{{jgcat|ku}}
</noinclude>
bf73c2r9wshbeu5lvdo6gdabd451vzg
6334644
6334552
2026-08-18T11:16:54Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Qanatê Kurdo/Tarîxa Edebyeta Kurdî/1983]] weke [[Şablon:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983]] guhart: Bot: Rûpel bar kir
6334552
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Qanatê Kurdo}}|sernav={{w|Tarîxa Edebyeta Kurdî}}|weşanxane={{w|Roja Nû}}|sal=1983}}<!--
-->{{kategorî|Jêgirtinên ji Qanatê Kurdo}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran (kurmancî)|Qanatê Kurdo]]
{{jgcat|ku}}
</noinclude>
bf73c2r9wshbeu5lvdo6gdabd451vzg
Gotûbêja şablonê:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983
11
933273
6334646
4840238
2026-08-18T11:16:55Z
Balyozbot
7867
Balyozbotî/ê navê [[Gotûbêja şablonê:Jêgirtin/Qanatê Kurdo/Tarîxa Edebyeta Kurdî/1983]] weke [[Gotûbêja şablonê:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983]] guhart: Bot: Rûpel bar kir
4840238
wikitext
text/x-wiki
{{r|Jiju}} <code><nowiki>{{jêder|{{Jêgirtin/Qanatê Kurdo/Tarîxa Edebyeta Kurdî/1983|r=249}}}}</nowiki></code> bi vî awayî--[[Bikarhêner:Balyozxane|Balyozxane]] ([[Gotûbêja bikarhêner:Balyozxane|gotûbêj]]) 17:39, 19 çiriya paşîn 2020 (UTC)
lqtjhcien9jp09crmucgx6m4de43k78
Şablon:J:Firat Cewerî:Kultur, huner û edebiyat:1996
10
933761
6334516
5883787
2026-08-18T00:33:13Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Kultur, huner û edebiyat/1996]] weke [[Şablon:J:Firat Cewerî:Kultur, huner û edebiyat:1996]] guhart
5883787
wikitext
text/x-wiki
{{pirtûk|nivîskar={{w|Firat Cewerî}}|sernav=Kultur, huner û edebiyat|weşanxane=Nûdem|sal=1996|rûpel={{{r|}}}|isbn=91 88592-251|ziman=ku}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
[[Kategorî: Çavkanî]]
</noinclude>
lkhmdvai6senhuq2r2i6w46smlxcu6n
6334518
6334516
2026-08-18T00:33:54Z
Wikihez
7344
6334518
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Firat Cewerî}}|sernav=Kultur, huner û edebiyat|weşanxane=Nûdem|sal=1996|isbn=91 88592-251}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
nax5sw2a7jhgelps31hmm1xiddktjib
Şablon:J:Öner:2017
10
933801
6334420
5883773
2026-08-17T23:00:43Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ahmet Öner/Di edebiyata kurdî ya gelêrî da di’a û nifir/2017]] weke [[Şablon:J:Öner:2017]] guhart
5883773
wikitext
text/x-wiki
{{pirtûk|nivîskar=Ahmet Öner|sernav={{w|Di edebiyata Kurdî ya Gelêrî da di’a û nifir}}|cîh={{w|Van}}|weşanxane={{w|Zanîngeha Wan Yûzûncû Yîlê Enstîtuya Zimanên Zindî Şaxa Makezanîsta Ziman û Çanda Kurdî Teza Lîsansa Bilind}}|sal={{w|2017}}|rûpel={{{r|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ahmet Öner}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ahmet Öner]]
[[Kategorî: Çavkanî]]
</noinclude>
gvz3n82nwxraqrnfyn27hsprz80413m
6334422
6334420
2026-08-17T23:01:33Z
Wikihez
7344
6334422
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Ahmet Öner|sernav={{w|Di edebiyata Kurdî ya Gelêrî da di’a û nifir}}|cîh={{w|Van}}|weşanxane={{w|Zanîngeha Wan Yûzûncû Yîlê Enstîtuya Zimanên Zindî Şaxa Makezanîsta Ziman û Çanda Kurdî Teza Lîsansa Bilind}}|sal={{w|2017}}|rûpel={{{r|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ahmet Öner}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ahmet Öner]]
{{jgcat|ku}}
</noinclude>
8q05phiyzw6msxx8e1n2sg57i5t5tso
6334423
6334422
2026-08-17T23:03:12Z
Wikihez
7344
6334423
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Ahmet Öner|sernav={{w|Di edebiyata Kurdî ya Gelêrî da di’a û nifir}}|cîh={{w|Van}}|weşanxane={{w|Zanîngeha Wan Yûzûncû Yîlê}} Enstîtuya Zimanên Zindî Şaxa Makezanîsta Ziman û Çanda Kurdî Teza Lîsansa Bilind|sal=2017|rûpel={{{r|{{{rûpel|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ahmet Öner}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ahmet Öner]]
{{jgcat|ku}}
</noinclude>
in2kc2xv5gfckb5uqwse4bv5c5ta3xu
6334424
6334423
2026-08-17T23:03:28Z
Wikihez
7344
6334424
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Ahmet Öner|sernav={{w|Di edebiyata Kurdî ya Gelêrî da di’a û nifir}}|cîh={{w|Van}}|weşanxane={{w|Zanîngeha Wan Yûzûncû Yîlê}} Enstîtuya Zimanên Zindî Şaxa Makezanîsta Ziman û Çanda Kurdî Teza Lîsansa Bilind|sal=2017|rûpel={{{r|{{{rûpel|}}}}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ahmet Öner}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ahmet Öner]]
{{jgcat|ku}}
</noinclude>
baz2gf6nnce1wwhk9n2316t9988oz5f
6334425
6334424
2026-08-17T23:03:54Z
Wikihez
7344
6334425
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Ahmet Öner|sernav=Di edebiyata Kurdî ya Gelêrî da di’a û nifir|cîh={{w|Van}}|weşanxane={{w|Zanîngeha Wan Yûzûncû Yîlê}} Enstîtuya Zimanên Zindî Şaxa Makezanîsta Ziman û Çanda Kurdî Teza Lîsansa Bilind|sal=2017|rûpel={{{r|{{{rûpel|}}}}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ahmet Öner}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ahmet Öner]]
{{jgcat|ku}}
</noinclude>
07nynsxij1j9inbgnpn5dtw6z649j9l
6334427
6334425
2026-08-17T23:05:07Z
Wikihez
7344
6334427
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Ahmet Öner|sernav=Di edebiyata Kurdî ya Gelêrî da di’a û nifir|cîh={{w|Van}}|weşanxane={{w|Zanîngeha Wanê|Zanîngeha Wan Yûzûncû Yîlê}} Enstîtuya Zimanên Zindî Şaxa Makezanîsta Ziman û Çanda Kurdî Teza Lîsansa Bilind|sal=2017|rûpel={{{r|{{{rûpel|}}}}}}}}<!--
-->{{kategorî|Jêgirtinên ji Ahmet Öner}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ahmet Öner]]
{{jgcat|ku}}
</noinclude>
oqss1gubt8bds0i2uh1hgkxnl4b4yho
Şablon:Jêgirtin/lîste
10
935499
6334690
4859735
2026-08-18T11:20:23Z
Wikihez
7344
6334690
wikitext
text/x-wiki
<includeonly>{{şl|J:{{{1}}}}} <small>([[Taybet:WhatLinksHere/Şablon:J:{{{1}}}|'''lîsteya peyvan''']])</includeonly></small><noinclude>
{{belgekirin}}
</noinclude>
716oneb1a7vtsp9s5cxtm9swxtx2idb
Şablon:J:Mehmed Uzun
10
935500
6334608
5883801
2026-08-18T11:16:17Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun]] weke [[Şablon:J:Mehmed Uzun]] guhart: Bot: Rûpel bar kir
5883801
wikitext
text/x-wiki
{{nivîskar|{{w|Mehmed Uzun}}}}
== Lîsteya pirtûkan ==
* {{Jêgirtin/lîste|Mehmed Uzun/Bîra qederê/2002}}
* {{Jêgirtin/lîste|Mehmed Uzun/Hawara Dîcleyê 1/2003}}
* {{Jêgirtin/lîste|Mehmed Uzun/Hêz û Bedewiya Pênûsê/1993}}
* {{Jêgirtin/lîste|Mehmed Uzun/Mirina Kalekî Rind/1987}}
* {{Jêgirtin/lîste|Mehmed Uzun/Siya Evînê/1989}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
itk78budfi1zg8t5v2pi4s3w83qhcdt
6334691
6334608
2026-08-18T11:21:10Z
Wikihez
7344
6334691
wikitext
text/x-wiki
{{nivîskar|{{w|Mehmed Uzun}}}}
== Lîsteya pirtûkan ==
* {{Jêgirtin/lîste|Mehmed Uzun:Bîra qederê:2002}}
* {{Jêgirtin/lîste|Mehmed Uzun:Hawara Dîcleyê 1:2003}}
* {{Jêgirtin/lîste|Mehmed Uzun:Hêz û Bedewiya Pênûsê:1993}}
* {{Jêgirtin/lîste|Mehmed Uzun:Mirina Kalekî Rind:1987}}
* {{Jêgirtin/lîste|Mehmed Uzun:Siya Evînê:1989}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Mehmed Uzun]]
7xooy671t3o4g2s196htpp4ccpaxvs3
Şablon:J:Robert Louis Stevenson:Meseleya Seyr a Dr. Jekyll û Mr. Hyde
10
969032
6334553
5883820
2026-08-18T01:00:57Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334553
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=Robert Louis Stevenson|sernav=Meseleya Seyr a Dr. Jekyll û Mr. Hyde|wergêr={{w|Ciwanmerd Kulek}}|cîh=Stenbol|weşanxane={{w|Avesta}}|sal=2020|isbn=978-605-2246-80-1}}<includeonly>{{kategorî|Jêgirtinên ji Ciwanmerd Kulek}}</includeonly><noinclude>
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ciwanmerd Kulek]]
[[Kategorî:Çavkanî]]
</noinclude>
7fgun2r9hs78tff5qoif2nu54bw7054
6334648
6334553
2026-08-18T11:17:02Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Robert Louis Stevenson/Meseleya Seyr a Dr. Jekyll û Mr. Hyde]] weke [[Şablon:J:Robert Louis Stevenson:Meseleya Seyr a Dr. Jekyll û Mr. Hyde]] guhart: Bot: Rûpel bar kir
6334553
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=Robert Louis Stevenson|sernav=Meseleya Seyr a Dr. Jekyll û Mr. Hyde|wergêr={{w|Ciwanmerd Kulek}}|cîh=Stenbol|weşanxane={{w|Avesta}}|sal=2020|isbn=978-605-2246-80-1}}<includeonly>{{kategorî|Jêgirtinên ji Ciwanmerd Kulek}}</includeonly><noinclude>
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ciwanmerd Kulek]]
[[Kategorî:Çavkanî]]
</noinclude>
7fgun2r9hs78tff5qoif2nu54bw7054
Şablon:J:Firat Cewerî:Payiza Dereng
10
969219
6334519
5883788
2026-08-18T00:35:35Z
Wikihez
7344
6334519
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[:Kategorî:Jêgirtinên ji Firat Cewerî|Firat Cewerî]]|sernav={{w|Payiza Dereng (pirtûk)|Payiza Dereng}}|weşanxane={{{weşanxane|Nûdem}}}|sal={{{sal|2005}}}}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
d9y89mwl8qgkioutxckktxkwqbv7o7s
6334520
6334519
2026-08-18T00:35:55Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Payiza Dereng]] weke [[Şablon:J:Firat Cewerî:Payiza Dereng]] guhart
6334519
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[:Kategorî:Jêgirtinên ji Firat Cewerî|Firat Cewerî]]|sernav={{w|Payiza Dereng (pirtûk)|Payiza Dereng}}|weşanxane={{{weşanxane|Nûdem}}}|sal={{{sal|2005}}}}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
d9y89mwl8qgkioutxckktxkwqbv7o7s
6334523
6334520
2026-08-18T00:38:06Z
Wikihez
7344
6334523
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[:Kategorî:Jêgirtinên ji Firat Cewerî|Firat Cewerî]]|sernav={{w|Payiza Dereng (pirtûk)|Payiza Dereng}}|weşanxane={{{weşanxane|Nûdem}}}|sal=2005|sala-weşandinê={{{sal|}}}}}<!--
-->{{kategorî|Jêgirtinên ji Firat Cewerî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Firat Cewerî]]
{{jgcat|ku}}
</noinclude>
tdkc7n4kldocfgzfvigi1tr41hn2np1
pelasîpoş
0
969718
6334477
6285576
2026-08-17T23:52:25Z
Wikihez
7344
6334477
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Rengdêr ===
{{rengdêr|ku}}
# Kesê/a ku bi [[cil]]ên xwe [[reben]] û [[belengaz]] xuya dike.
#: {{hevmaneyên peyvê|ku|feqîr|reben}}
#* {{Jêgirtin/Ehmedê Xanî/Dîwana Ehmedê Xanî|jêgirtin=Şahîn û bi xef, bi libsî dîba</br>Zahir ji xwe em '''pelasîpoş''' în}}
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
{{werger-bin}}
9nvb8zkg3x3wvcj0rq9ctb247oklzm9
生物学
0
1197173
6334381
5526695
2026-08-17T19:10:19Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334381
wikitext
text/x-wiki
== {{ziman|ja}} ==
=== Navdêr ===
{{ja-navdêr|せいぶつがく}}
# {{mijar|ja|zanist}} {{g|ku|biyolojî}}
=== Çavkanî ===
{{rûpela wergerê|ja|biyolojî}}
j3fit83tghv4gm0xup3tufax9ji7rac
6334389
6334381
2026-08-17T19:20:23Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: Ji [[en:生物学|en.wikt]]
6334389
wikitext
text/x-wiki
== {{ziman|ja}} ==
=== Bilêvkirin ===
{{ja-pron|せいぶつがく|acc=4|acc_ref=NHK,DJR}}
=== Navdêr ===
{{ja-navdêr|せいぶつがく}}
# {{mijar|ja|zanist}} {{g|ku|biyolojî}}
=== Çavkanî ===
{{rûpela wergerê|ja|biyolojî}}
i2bc970ceqbzjw9f3d4xmsliu6zjg1s
6334390
6334389
2026-08-17T19:20:57Z
Wikihez
7344
/* Çavkanî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334390
wikitext
text/x-wiki
== {{ziman|ja}} ==
=== Bilêvkirin ===
{{ja-pron|せいぶつがく|acc=4|acc_ref=NHK,DJR}}
=== Navdêr ===
{{ja-navdêr|せいぶつがく}}
# {{mijar|ja|zanist}} {{g|ku|biyolojî}}
=== Çavkanî ===
{{çavkanî}}
lilp1b6zsy827jhs0k7ye69suib61dn
Şablon:J:Îrfan Amîda:Şevek Şîzofren
10
1261655
6334573
5883834
2026-08-18T01:08:03Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334573
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Îrfan Amîda}}|sernav=Şevek Şîzofren|weşanxane={{w|Weşanên Lîs}}|sal=2018|isbn=9786058152175}}<!--
-->{{kategorî|Jêgirtinên ji Îrfan Amîda}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Îrfan]]
[[Kategorî:Çavkanî]]
</noinclude>
2l7hap6p1ruu0wb8b3xing6oim7r5b5
6334688
6334573
2026-08-18T11:17:30Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Îrfan Amîda/Şevek Şîzofren]] weke [[Şablon:J:Îrfan Amîda:Şevek Şîzofren]] guhart: Bot: Rûpel bar kir
6334573
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Îrfan Amîda}}|sernav=Şevek Şîzofren|weşanxane={{w|Weşanên Lîs}}|sal=2018|isbn=9786058152175}}<!--
-->{{kategorî|Jêgirtinên ji Îrfan Amîda}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Îrfan]]
[[Kategorî:Çavkanî]]
</noinclude>
2l7hap6p1ruu0wb8b3xing6oim7r5b5
소금병
0
1265793
6334356
5784058
2026-08-17T15:41:49Z
YeBoy371
7604
/* Navdêr */
6334356
wikitext
text/x-wiki
== {{ziman|ko}} ==
=== Navdêr ===
{{navdêr|ko|sc=Kore}}
# {{f|ko|aman}} {{g|ku|xwêdank}}
#: {{hevmane|ko|소금통}}
=== Çavkanî ===
{{rûpela wergerê|ko|xwêdank}}
tajbyd6fhcy0y7rbue3ufhaimwlk07s
소금통
0
1324539
6334357
6029344
2026-08-17T15:42:02Z
YeBoy371
7604
/* Navdêr */
6334357
wikitext
text/x-wiki
== {{ziman|ko}} ==
=== Navdêr ===
{{navdêr|ko|sc=Kore}}
# {{f|ko|aman}} {{g|ku|xwêdank}}
#: {{hevmane|ko|소금병}}
=== Çavkanî ===
{{rûpela wergerê|ko|xwêdank}}
p0tud1kbu9l1jh1fwi3xzhlihld9q08
Bikarhêner:Wikihez/skrîpt
2
1324934
6334416
6291555
2026-08-17T22:52:30Z
Wikihez
7344
/* JSON */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334416
wikitext
text/x-wiki
<center>
<inputbox>
type=create
preload=Bikarhêner:Wikihez/skrîpt/preload
default=Bikarhêner:Wikihez/skrîpt/
buttonlabel=Çêke
width=50
</inputbox>
</center>
* [[Bikarhêner:Wikihez/common.js|common.js]]
* [[Bikarhêner:Wikihez/common.css|common.css]]
* [[Bikarhêner:Wikihez/test.js|test.js]]
* [[Bikarhêner:Wikihez/test.py|test.py]]
* [[Bikarhêner:Wikihez/test.json|test.json]]
==JSON==
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/json/|stripprefix=yes|hideredirects=yes}}
{{Special:PrefixIndex/Şablon:J:|stripprefix=yes|hideredirects=yes}}
==Python==
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/py/|stripprefix=yes|hideredirects=yes}}
==Javascript==
===JS info===
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/înfo/|stripprefix=yes|hideredirects=yes}}
===Skrîpt===
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/js/|stripprefix=yes|hideredirects=yes}}
===Gadget===
{{Special:PrefixIndex/Bikarhêner:Wikihez/Gadget-|stripprefix=yes|hideredirects=yes}}
6li8mf3oddy221d1pytg2v413x1jym4
6334419
6334416
2026-08-17T23:00:00Z
Wikihez
7344
/* JSON */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334419
wikitext
text/x-wiki
<center>
<inputbox>
type=create
preload=Bikarhêner:Wikihez/skrîpt/preload
default=Bikarhêner:Wikihez/skrîpt/
buttonlabel=Çêke
width=50
</inputbox>
</center>
* [[Bikarhêner:Wikihez/common.js|common.js]]
* [[Bikarhêner:Wikihez/common.css|common.css]]
* [[Bikarhêner:Wikihez/test.js|test.js]]
* [[Bikarhêner:Wikihez/test.py|test.py]]
* [[Bikarhêner:Wikihez/test.json|test.json]]
==JSON==
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/json/|stripprefix=yes|hideredirects=yes}}
{{Special:PrefixIndex/Şablon:Jêgirtin/|stripprefix=yes|hideredirects=yes}}
==Python==
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/py/|stripprefix=yes|hideredirects=yes}}
==Javascript==
===JS info===
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/înfo/|stripprefix=yes|hideredirects=yes}}
===Skrîpt===
{{Special:PrefixIndex/Bikarhêner:Wikihez/skrîpt/js/|stripprefix=yes|hideredirects=yes}}
===Gadget===
{{Special:PrefixIndex/Bikarhêner:Wikihez/Gadget-|stripprefix=yes|hideredirects=yes}}
njkswedpfxevkmr402l1rve30b91utq
Şablon:îzaha maneyî/belge
10
1325229
6334354
6329230
2026-08-17T15:38:45Z
Wikihez
7344
6334354
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
{{shortcut|Template:gl}}
{{uses lua|Modul:links/templates}}
Use this template for formatting of [[Wiktionary:Style guide#Types of definitions|glosses]] in non-English entries, those that come after the translation.
An example of use in Swahili entry for [[jicho]]:
<syntaxhighlight lang="wikitext">
==Swahili==
===Noun===
{{sw-noun|ma}}
# [[eye]] {{gloss|organ of vision}}
</syntaxhighlight>
Formatting: The template currently renders its parameter text in plain parentheses, in roman. In future, it can be made customizable to render it in italic or whatever users prefer. A model for customization is {{temp|sense}}.
Model templates: {{temp|sense}}.
==See also==
* {{temp|non-gloss definition}}
==TemplateData==
<includeonly>{{TemplateData header}}</includeonly>
<templatedata>
{
"params": {
"1": {
"label": "gloss",
"description": "The gloss for this entry",
"example": "organ of vision",
"type": "string",
"required": true
}
},
"description": "Use this template for formatting of glosses in non-English entries, those that come behind translation.",
"format": "inline"
}
</templatedata>
<includeonly>
[[Kategorî:Şablon]]
</includeonly>
cu6n0aatjv7hlgjrn051ysse7ra627w
Şablon:J:Hawar:1998:1
10
1325432
6334454
6112960
2026-08-17T23:26:35Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 1 Hejmar 1-23 (1932-1933)/1998]] weke [[Şablon:J:Hawar:1998:1]] guhart
6112960
wikitext
text/x-wiki
<includeonly>{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kovar|ku|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|kovar=[[w:HAWAR|Hawar]]|sernav={{{sernav|}}}|nivîskar={{{nivîskar|}}}|amadekar={{w|Firat Cewerî}}|wergêr={{{terciman|{{{wergêr|}}}}}}|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|cîld=1|hejmar={{#if:{{{hejmar|{{{hj|}}}}}}|{{{hejmar|{{{hj|}}}}}}|1-23}}|sal={{#if:{{{sal|}}}|{{{sal|}}}|1920-193}}|rûpel={{{r|}}}}}</includeonly><noinclude>
{{belgekirin|naverok=
{{kurterê|Şablon:Hawar1}}
<templatedata>
{
"params": {
"sernav": {},
"nivîskar": {},
"hejmar": {
"aliases": [
"hj"
]
},
"r": {
"label": "rûpel"
},
"terciman": {
"aliases": [
"wergêr"
],
"label": "terciman",
"description": "terciman"
},
"sal": {
"label": "sal"
}
},
"paramOrder": [
"hejmar",
"sal",
"sernav",
"nivîskar",
"terciman",
"r"
]
}
</templatedata>}}
[[Kategorî:Çavkanî]]
</noinclude>
sndrw9mxaz00edbz9lxlr2nphflk1xn
6334456
6334454
2026-08-17T23:28:39Z
Wikihez
7344
6334456
wikitext
text/x-wiki
<includeonly>{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kovar|ku|jêgirtin={{{jêgirtin|{{{jg|}}}}}}|kovar=[[w:HAWAR|Hawar]]|sernav={{{sernav|}}}|nivîskar={{{nivîskar|}}}|amadekar={{w|Firat Cewerî}}|wergêr={{{terciman|{{{wergêr|}}}}}}|weşanxane=Weşanên [[w:Nûdem|Nûdem]]|cîld=1|hejmar={{#if:{{{hejmar|{{{hj|}}}}}}|{{{hejmar|{{{hj|}}}}}}|1-23}}|sal={{#if:{{{sal|}}}|{{{sal|}}}|1920-193}}|rûpel={{{r|}}}}}</includeonly><noinclude>
{{belgekirin|naverok=
{{kurterê|Şablon:Hawar1}}
<templatedata>
{
"params": {
"sernav": {},
"nivîskar": {},
"hejmar": {
"aliases": [
"hj"
]
},
"r": {
"label": "rûpel"
},
"terciman": {
"aliases": [
"wergêr"
],
"label": "terciman",
"description": "terciman"
},
"sal": {
"label": "sal"
}
},
"paramOrder": [
"hejmar",
"sal",
"sernav",
"nivîskar",
"terciman",
"r"
]
}
</templatedata>}}
{{jgcat|ku}}
</noinclude>
8munvuh3xplqbw3jwusfx69zuhynl4o
Şablon:Ç:ja:Daijirin
10
1326083
6334385
6111521
2026-08-17T19:19:42Z
Wikihez
7344
6334385
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|ja
|editor=[[w:ja:松村明|Matsumura, Akira]]
|year=2006
|title={{lw|ja|大辞林}}
|trans-title={{w|ziman=en|Daijirin}}
|location=w:Tokyo
|publisher=w:en:Sanseidō
|isbn=4-385-13905-9
|edition=3
}}<noinclude>
{{shortcut|Template:R:ja:DJR|Template:R:ja:DJR3|Template:R:大辞林}}
{{documentation}}
{{refcat|ja}}
</noinclude>
osls32f4rhtwg5xxba8nqx9ret7d3uj
6334386
6334385
2026-08-17T19:19:58Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:Daijirin]] weke [[Şablon:Ç:ja:Daijirin]] guhart
6334385
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|ja
|editor=[[w:ja:松村明|Matsumura, Akira]]
|year=2006
|title={{lw|ja|大辞林}}
|trans-title={{w|ziman=en|Daijirin}}
|location=w:Tokyo
|publisher=w:en:Sanseidō
|isbn=4-385-13905-9
|edition=3
}}<noinclude>
{{shortcut|Template:R:ja:DJR|Template:R:ja:DJR3|Template:R:大辞林}}
{{documentation}}
{{refcat|ja}}
</noinclude>
osls32f4rhtwg5xxba8nqx9ret7d3uj
Şablon:Ç:ja:NHK Hatsuon
10
1326089
6334382
6111528
2026-08-17T19:15:20Z
Wikihez
7344
6334382
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|ja|editor={{w|ziman=ja|NHK放送文化研究所|NHK Broadcasting Culture Research Institute}}|year=1998|title=ja:NHK日本語発音アクセント辞典|trans-title=[[w:en:NHK|NHK]] Ferhenga Telafûzkirinê ya Japonî|location=w:Tokyo|publisher={{w|ziman=ja|NHK出版|NHK Publishing, Inc.}}|isbn=978-4-14-011112-3}}<noinclude>
{{documentation}}
{{refcat|ja|sort=NHK Ferhenga Telafûzkirinê ya Japonî}}
</noinclude>
61wvfvq17eukxcj3w0czs7nc3rsj7g4
6334383
6334382
2026-08-17T19:15:30Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:NHK Hatsuon]] weke [[Şablon:Ç:ja:NHK Hatsuon]] guhart
6334382
wikitext
text/x-wiki
{{#invoke:quote|call_template
|template=cite-book
|ja|editor={{w|ziman=ja|NHK放送文化研究所|NHK Broadcasting Culture Research Institute}}|year=1998|title=ja:NHK日本語発音アクセント辞典|trans-title=[[w:en:NHK|NHK]] Ferhenga Telafûzkirinê ya Japonî|location=w:Tokyo|publisher={{w|ziman=ja|NHK出版|NHK Publishing, Inc.}}|isbn=978-4-14-011112-3}}<noinclude>
{{documentation}}
{{refcat|ja|sort=NHK Ferhenga Telafûzkirinê ya Japonî}}
</noinclude>
61wvfvq17eukxcj3w0czs7nc3rsj7g4
Şablon:J:Sadiq Ûskan:Şampaz
10
1326856
6334556
6127835
2026-08-18T01:02:07Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334556
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Sadiq Ûskan|Sadiq Ûskan]]|sernav=[[w:Şampaz|Şampaz]]|weşanxane=Weşanxaneya [[w:Wardoz|Wardoz]]}}<!--
-->{{kategorî|Jêgirtinên ji Sadiq Ûskan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sadiq Ûskan]]
{{jgcat|ku}}
</noinclude>
dom57e4ouk1etvysa71jmkllglvy8ul
6334654
6334556
2026-08-18T11:17:07Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sadiq Ûskan/Şampaz]] weke [[Şablon:J:Sadiq Ûskan:Şampaz]] guhart: Bot: Rûpel bar kir
6334556
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=[[w:Sadiq Ûskan|Sadiq Ûskan]]|sernav=[[w:Şampaz|Şampaz]]|weşanxane=Weşanxaneya [[w:Wardoz|Wardoz]]}}<!--
-->{{kategorî|Jêgirtinên ji Sadiq Ûskan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sadiq Ûskan]]
{{jgcat|ku}}
</noinclude>
dom57e4ouk1etvysa71jmkllglvy8ul
Şablon:J:Sadiq Ûskan:Gurê Manco
10
1327001
6334555
6128602
2026-08-18T01:01:16Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334555
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Sadiq Ûskan}}|sernav=Gurê Manco|weşanxane={{w|Wardoz}}|sal=2024|isbn=978-605-2003-50-3}}<!--
-->{{kategorî|Jêgirtinên ji Sadiq Ûskan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sadiq Ûskan]]
{{jgcat|ku}}
</noinclude>
cfiunym1zn5r70svvnzy0rt1wyhrqod
6334652
6334555
2026-08-18T11:17:06Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sadiq Ûskan/Gurê Manco]] weke [[Şablon:J:Sadiq Ûskan:Gurê Manco]] guhart: Bot: Rûpel bar kir
6334555
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Sadiq Ûskan}}|sernav=Gurê Manco|weşanxane={{w|Wardoz}}|sal=2024|isbn=978-605-2003-50-3}}<!--
-->{{kategorî|Jêgirtinên ji Sadiq Ûskan}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Sadiq Ûskan]]
{{jgcat|ku}}
</noinclude>
cfiunym1zn5r70svvnzy0rt1wyhrqod
qilqaldar
0
1327024
6334331
6334298
2026-08-17T14:46:07Z
Balyozbot
7867
Bot: Sererastkirina şablonan û koda zimên
6334331
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Rengdêr ===
{{ku-tewîn-rd|qilqaldar}}
{{rengdêr|ku}}
# {{mane?|ku}}
#* {{nimûne?|ku}}
==== Jê ====
*[[qilqilîn]]
*[[qilqilandin]]
==== Etîmolojî ====
{{bêetîmolojî|ku}}
==== Werger ====
{{werger-ser}}
* {{Z|de}}: {{W+|de|beklommen|guman=erê}}
* {{Z|da}}: {{W-|da|urolig}}
* {{Z|eo}}: {{W-|eo|anksia}}
* {{Z|fi}}: {{W+|fi|levoton}}, {{W+|fi|huolestunut}}
* {{Z|fr}}: {{W+|fr|anxieux}}
* {{Z|nl}}: {{W+|nl|angstig}}, {{W+|nl|bezorgd}}, {{W+|nl|ongerust}}
* {{Z|en}}: {{W+|en|anxious}}, {{W+|en|concerned}}, {{W+|en|loath}}
* {{Z|it}}: {{W+|it|ansioso}}
* {{Z|pt}}: {{W+|pt|ansioso}} {{n}}, {{W+|pt|ansiosa}} {{mê}}
* {{Z|sv}}: {{W+|sv|orolig}}
* {{Z|tr}}: {{W+|tr|endişeli}}, {{W+|tr|kaygılı}}, {{W+|tr|huzursuz}}, {{W+|tr|sıkıntılı}}, {{W+|tr|telaşlı}}
{{werger-bin}}
t1dvms8pba1rwngk5dj0ahn8reujq2u
Wîkîferheng:Dîwan/2026
4
1327120
6334311
6334102
2026-08-17T12:54:57Z
Wikihez
7344
/* Guhartina rêbaznameyê */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334311
wikitext
text/x-wiki
<noinclude>{{Dîwan/arşîv-ser|2026}}</noinclude>
{{clear}}
== Daxwazên standardkirinê ==
https://ku.wiktionary.org/wiki/W%C3%AEk%C3%AEferheng:Daxwaz%C3%AAn_standardkirin%C3%AA, Gelo di vê malperê de peyvên hatîye standart kirin; ji malperê rakin? Zêde tehlîhev e. @[[Bikarhêner:Bikarhêner|Bikarhêner]]@[[Bikarhêner:Guherto|Guherto]] [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 10:03, 8 kanûna paşîn 2026 (UTC)
:@[[Bikarhêner:Rewiye tine|Rewiye tine]] [[Taybet:Tercîh#mw-prefsection-gadgets]] li vir amûra [[MediaWiki:Gadget-aWa.js/belgekirin|awa]] aktîv bike û pê re dikarî gotûbêjan arşîv bikî. Li sayfa [[WF:DS]] li kêleka her sernavê gotûbêjê <nowiki>[arşîv bike]</nowiki> derdikeve, pê lê bike û ew ê arşîv bike. Eger nikarî tu tenê şablona {{ş|ds-kirî}} îlawe bike, ez ê dûre arşîv bikim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:35, 5 tîrmeh 2026 (UTC)
== Standartkirin ==
Sala borî, min pêşnîyazek kiribû. Halî hazir piştî daxwaza standarkirinê em du heftî disekinin, eger kesek fikrek dîyar neke. Raste rast peyv li gor ferhenga standart a mezopotamyayê tê standart kirin. Ji xwe sazîyek vê karî dike çima em vê barî dikin stuyê xwe. Mesela ez çaxe di wîkîferhengê de peyvek digêrim; standart a peyvê jî li cem xwe kontrol dikim. Eger destûr hebe bi germî dikarim wê malperê standart bikim. Benda bûna du heftîyan gelo ne zehmet e? Duyemîn pêşnîyaza min nîşandana standarta peyvê di malpera de ye. Kesek dema malperan binêre, divê li jora malperan bibîne ku ev peyv standart e. Ev jî hîzmet e. Çimkî nivîskar, wergêr, edîtor, rojnemavan hez dikin bi peyvê standart binivîsin. https://ku.wiktionary.org/wiki/k%C3%AAf li vir wekî mînak min daye nîşan. Lê îfade eger wisa be: Standarda Rastnivîsîna Ferhenga Kurdî a Weqfa Mezopotamyayê kêf (r. 253) e yê çetir be. Çimkî îfadeya berê de peyva kurdî-tirkî derbas dibe û di mejîyê mirov de tevlihevî çêdibe.@[[Bikarhêner:Bikarhêner|Bikarhêner]]@[[Bikarhêner:Guherto|Guherto]] [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 10:25, 8 kanûna paşîn 2026 (UTC)
:Merheba. Hevalno dîyar e karê standarkirinê li wîkîferhengê rawestîya ye. Min sê peyv ji bo standartkirinê qeyd kir. Kes li ser şîrove nenivîsî. Mehek jî li ser derbas bû. Aniha ez dikarim van rupelan biguherim. Li gor qaîdeya hatine danîn wisa ye. Daxwaza min heta niha jî ew e kelîmeyên di ferhenga standartkirinê de heyî divê em bikaribin rasterast li vir jî bikin standart. An jî demê kurttir bikin. Li weqfa mezapotamyayê 20 mirov digêhin hev li ser peyvekî seetan nîqaş dikin. Ma hewceyê em jî li vir nîqaş bikin? Tevlî vê jî ji xwe kes li ser vê mijare nasekîne van dema. Xeynî vê pêşnîyaza min nîşandana standarta peyvê di malpera de hebû. Li ser malperê 'Standarda Rastnivîsîna Ferhenga Kurdî a Weqfa Mezopotamyayê kêf (r.253) e' Tiştek mîna wî hebe yê gelek kêrî her cure nivîskarê me were. Çimkî hin nivîskarên me hez dike bi kelîmeyên standart binivîse. Di malperê de bila vê hêsanî fam bike çêtir e. @[[Bikarhêner:Bikarhêner|Bikarhêner]]@[[Bikarhêner:Ghybu|Ghybu]] [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 18:35, 17 sibat 2026 (UTC)
::Merheba @[[Bikarhêner:Rewiye tine|Rewiye tine]]. Ev format pir xirab dibe. Tu dikarî li beşa Çavkaniyê binivîsî bi şablona {{ş|kitêb}} binivîsî çêtir e û tê eynî maneyê. Lê her kes standarda Weqfa Mezopotamyayê qebûl nakin û ez jî şibhê dikim ku ev websîte bi awayekî kolektîv tê çêkirin an na. Loma em bi ya kitêba rastnivisînê çêkin û eger îtîraz tinebe, şablona {{ş|ds-kirî}} li gotûbêja [[WF:DS]] binivîse, dûre li gotûbêjê arşîv bike, ne hewce ye li hêviya kesên din bisekinî. Eger itirazek hebe, ê ku dixwaze dûre dîsa şablona ds zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:30, 5 tîrmeh 2026 (UTC)
:::[https://berwarnews.com/ferhenga-rastnivisine-qonaxeke-giring-a-standardkirina-kurmanci-ye/ Roportaja Mikail Bülbül] li ser proseya standardkirinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:57, 9 tîrmeh 2026 (UTC)
::::Min nû dît an jî nû hatiye zêdekirin lê meqaleyek bi navê [https://www.ferhengkurdi.com/?q=iltihap&modal=lijne lijneya edîtorîyê] nivîsandine [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:40, 22 tîrmeh 2026 (UTC)
== <span lang="en" dir="ltr">Annual review of the Universal Code of Conduct and Enforcement Guidelines</span> ==
<div lang="en" dir="ltr">
<section begin="announcement-content" />
I am writing to you to let you know the annual review period for the Universal Code of Conduct and Enforcement Guidelines is open now. You can make suggestions for changes through 9 February 2026. This is the first step of several to be taken for the annual review. [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2026|Read more information and find a conversation to join on the UCoC page on Meta]].
The [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee|Universal Code of Conduct Coordinating Committee]] (U4C) is a global group dedicated to providing an equitable and consistent implementation of the UCoC. This annual review was planned and implemented by the U4C. For more information and the responsibilities of the U4C, [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Charter|you may review the U4C Charter]].
Please share this information with other members in your community wherever else might be appropriate.
-- In cooperation with the U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]])<section end="announcement-content" />
</div>
21:02, 19 kanûna paşîn 2026 (UTC)
<!-- Message sent by User:Keegan (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=29905753 -->
== Request for comment (global AI policy) ==
<bdi lang="en" dir="ltr" class="mw-content-ltr">
Apologies for writing in English. {{int:Please-translate}}
A [[:m:Requests for comment/Artificial intelligence policy|request for comment]] is currently being held to decide on a global AI policy. {{int:Feedback-thanks-title}}
[[Bikarhêner:MediaWiki message delivery|MediaWiki message delivery]] ([[Gotûbêja bikarhêner:MediaWiki message delivery|gotûbêj]]) 00:58, 26 nîsan 2026 (UTC)
</bdi>
<!-- Message sent by User:Codename Noreste@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30424282 -->
== Guharto ==
: <small>→ Ji rûpela ''[//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj:berf%C3%AEtik&oldid=6233338 Gotûbêj:berfîtik]'' hatiye girtin.</small>
Silav {{r|Ghybu}} ma hewce ye em guhartoyan jî kategorîze bikin? Li vê [[ژیشک]] binere, tije guhartoyên wê hene, ku em ji gişan re guhandar binivîsin dê 10 navên heman hebin di nav wê kategorîyê de. Tu çi difikirî? [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 09:40, 26 nîsan 2026 (UTC)
:{{silav|Keyomerd}}. Erê, problem çî ye? {{=)}} Ew jî tên bikaranîn... Dema ku peyvek tê çêkirin hewce ye ku em hemû agahiyên wan binivîsin: bilêvkirin, bikaranîn, mînak, terîm, herêm, tewandin, etîmolojî...
:Bi min peyvên ku wekî ''guherto'' hatine nivîsandin bi nivîsa {{w|Italic type|îtalîk|ziman=en}} (xwahr) di nav kategoriyan de werin nişan kirin (ji bo cudakirinê). {{ping|Ferhengvan|Bikarhêner}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:40, 30 nîsan 2026 (UTC)
::Yên '''''nestandard''''' di kategorîyan de biferq werin nîşandan baştir e. Qene em karibin wan ji yên stadard ferq bikin. @[[Bikarhêner:Ghybu|Ghybu]] Xêra xwe hûn sîstemeke wisa çêkin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:36, 1 gulan 2026 (UTC)
:::Merheba @[[Bikarhêner:Keyomerd|Keyomerd]] û @[[Bikarhêner:Bikarhêner|Bikarhêner]] ev xisûsiyet jixwe di şablonên rêzimanê de heye, wextê {{param|formaalt|1}} dinivîsin, made dikeve Kategoriya [[:Kategorî:Formên alternatîv bi kurmancî]] li gorî koda zimanî jî zimanên din tê çêkirin. Hemû madeyên kurmancî bi alfabeya erebî dikevin vê kategoriyê lê nizanim ev tiştekî rast e Yan xelet e, ji bo nîşandanê min çêkiribû lê min hîn wext nedît ku nîqaşek li ser çêkim. Qaîdeyên bikaranîna vê parametreyê di wîkiya îngilîzî de jî tam ne belî ye. Du gotûbêj [https://en.wiktionary.org/wiki/Wiktionary:Requests_for_moves,_mergers_and_splits#Category:Alternative_forms_by_language] [https://en.wiktionary.org/wiki/Wiktionary:Beer_parlour/2024/June#Decluttering_the_altform_mess] çêkirine lê ji bo îngilîzî ev parametre nehatiye bikaranîn. Min ji bo nîşandanê niha [https://ku.wiktionary.org/w/index.php?title=bax%C3%A7%C3%AA_zarokan&diff=prev&oldid=6241372] ceriband lê bi min xelet e. Em çawa bikin? Kengî vê parametreyê bi kar bînin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:29, 8 tîrmeh 2026 (UTC)
::::@[[Bikarhêner:Kurdanparez|Kurdanparez]] Tu çi difikirî? Ji bo zazakî [https://ku.wiktionary.org/w/index.php?title=a_c%C4%B1_geyrayene&oldid=6241396 wisa] bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:55, 8 tîrmeh 2026 (UTC)
:::::Kategorîyekê cuda baş dibe. Wextê ku ez li kategorîya "lekêren bi zazakî" dinêrim, ez hem formê bi şikilê Johnsonê û formê standardê dibînim. Zaten wexte ku ez dikevim ê Johnsonê, min dibe formê standardê. Kategorîyekê cuda, bi taybetî ji bo nivîsandinên alternatîf, baş dibe.
:::::Lê em bîr nekin ku wextê ku em bo formên alternatîf yên devokî qal dikin, belkî adil neye ku em ev bişînin derekê din yê ku însan ewqas nabînin. [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 22:03, 8 tîrmeh 2026 (UTC)
::::::@[[Bikarhêner:Kurdanparez|Kurdanparez]] Gelo min rast fêm kir? Tu dibêjî formên {{q|ku|kîvroşk}} bi dîqet werin guhartokirin lê bi yên bi alfabeya Jacobsonê hemû bikin {{param|formaalt|1}}? Formên Jacobsonê dê bikevin Kategoriya [[:Kategorî:Lêker bi zazakî|Lêker bi zazakî]] û [[:Kategorî:Lema bi zazakî|Lema bi zazakî]] yan na? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:55, 8 tîrmeh 2026 (UTC)
:::::::Bi min naxwaze di wan kategoriyan de bikeve. Wek tu dibînî dûplîkat pir hene. Bi min çareya baştir yên Johnsonê di katehgorîyekê din bikevin (bila format li rûpelan neyin guherandin, bi tenê kategorî) [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 00:37, 9 tîrmeh 2026 (UTC)
== Peyvên soranî bi latînî ([[:Kategorî:Soranî]]) ==
Silav, di nav vê kategoriyê (''[[:Kategorî:Soranî]]'') de pirr peyv tenê bi alfabeya latînî hatine nivîsandin. Divê ew mutleq jî bi alfabeya erebî werin transkrîbe kirin ji ber ku soranî bi alfabeya erebî tê nivîsand... Bikarênerên ku soranî dizanin dikarin wan sererast bikin? Mînakên guhertinan: [//ku.wiktionary.org/w/index.php?title=zor%C3%AEne&curid=6584&diff=6233355&oldid=5859303], [//ku.wiktionary.org/w/index.php?title=s%C3%AAw&diff=6233097&oldid=6233003], [//ku.wiktionary.org/w/index.php?title=sewz&diff=6233313&oldid=6069107], ... Spas! [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:16, 2 gulan 2026 (UTC)
== <span lang="en" dir="ltr">Vote now in the 2026 U4C election</span> ==
<div lang="en" dir="ltr">
<section begin="announcement-content" />
Eligible voters are asked to participate in the 2026 [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee]] election. More information–including an eligibility check, voting process information, candidate information, and a link to the vote–are available on Meta at the [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|2026 Election information page]]. The vote closes on 2 June 2026 at [https://zonestamp.toolforge.org/1780358400 00:00 UTC].
Please vote if your account is eligible. Results will be available by 14 June 2026. -- In cooperation with the U4C,<section end="announcement-content" />
</div>
[[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 17:15, 27 gulan 2026 (UTC)
<!-- Message sent by User:Keegan (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== RFC about AI-generated content in Wikimedia Commons ==
<bdi lang="en" dir="ltr">Apologies for writing in English, please help translate this message to your language. You are invited to participate in a [[c:Commons:Requests for comment/Policy update for AI content|request for comment on Wikimedia Commons about a policy update for AI content]]. This may affect files that are uploaded to Wikimedia Commons for use on this project. Thank you. [[m:User:Codename Noreste|Codename Noreste]] ([[m:User talk:Codename Noreste|gotûbêj]])</bdi> 17:12, 23 hezîran 2026 (UTC)
<!-- Message sent by User:Codename Noreste@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== <span lang="en" dir="ltr">Deployment of Legal and Safety Contacts Link in the Footer of Your Wiki</span> ==
<div lang="en" dir="ltr">
<section begin="Message"/>
'''Legal & Safety Contacts'''
Hello community, the Wikimedia Foundation has provided a [[wmf:Special:MyLanguage/Legal:Wikimedia Foundation Legal and Safety Contact Information|single legal and safety contact page]], to be linked in the footer of your wiki, to ensure access to accurate legal information. This is a regulatory requirement. We have already rolled out links to English, German, Italian, Spanish and other wikis and we will deploy to your wiki soon. [[m:Special:MyLanguage/Wikimedia_Foundation_Legal_and_Safety_Contacts_FAQ|Please read more on the project page]] and leave any comments in this thread or on the [[m:Special:MyLanguage/Talk:Wikimedia Foundation Legal and Safety Contacts FAQ|talk page]].
<section end="Message"/>
</div>
-- [[User:Sannita (WMF)|User:Sannita (WMF)]] ([[User talk:Sannita (WMF)|talk]]) 13:31, 25 hezîran 2026 (UTC)
<!-- Message sent by User:Sannita (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=User:Sannita_(WMF)/Mass_sending_test&oldid=30731267 -->
== Jêbirina ferhenga Sadallah ==
Merheba @[[Bikarhêner:Guherto|Guherto]] Îmkan heye lîsteyeke wisa çêkî ku hemû sayfeyên Cyrusbot çêkir yên ku tê de tenê tercimeyeke îngilîzî heye û ev tercime jî hatibe jêbirin? Nimûne ''[[derdzanî]], [[êşzanî]]'' tenê tercimeya [[pestology]] heye ew jî ji ber heqê telîfê hatiye jêbirin. Nimûneyeke din ''zêrrgeh'', ''zêrrkar'' [[goldsmith]] hatiye jêbirin zêr wekî zêrr hatiye guhartin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:48, 26 hezîran 2026 (UTC)
:Temam @[[Bikarhêner:Wikihez|Wikihez]], lê vê heyvê ez nikarim, heyva were ez ê li vê meseleyê vegerim înşellah. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 22:51, 2 tebax 2026 (UTC)
== Beşa Tewandin bo kurmancî ==
Bi ya min şablona {{ş|ku-tewîn-nav}} û ya fiîlê em bixin beşa Tewandinê (wekî [[şodir|zazakî]]. Bi vî awayî hem ji aliyê şiklî ve hem jî ji aliyê bikaranînê ve xweştir xuya bike. Eger di qismê Tewandinê de be, ê ku dixwaze îzah û notên xwe yan jî emilandinên din jî dikare binivîse. Hûn çi dibêjin? @[[Bikarhêner:Ferhengvan|Ferhengvan]], @[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Bikarhêner|Bikarhêner]]. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 6 tîrmeh 2026 (UTC)
:Belkî em dikarin Formata şiweyan wekî ya behdînî jî têxin nav şablonê. Pir caran şiklê tabloya Tercimeyan ji ber dirêjbûna şablona teqînê jî xirab dibe wekî [[astrofizîk]]. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 6 tîrmeh 2026 (UTC)
:Min mijarê te li jorê yê xwe dît, fikirên min jî hene eger baş e. Yê ku te gotiye bi min baş dibe, ji ber ku her rûpel ji vê mîna hev dibe. Em ê rindtir jî ferq li nav devokan binivîsin. [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 11:44, 7 tîrmeh 2026 (UTC)
::@[[Bikarhêner:Bikarhêner|Bikarhêner]] fikr? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:44, 15 tîrmeh 2026 (UTC)
:::Formata ku te teklîf kirîye jî ne xerab e, baş tê xuyan. Feqet bi min halê berê (mewcûd) baştir e. Ji ber ku ev şablon (ku-tewîn-nav) bi piranî kurt in û ji bo her kesî ne mihîm e, lewma li kinarê bi formeke hebe ku karibe were vekirin û girtin baştir e. Lê belê ger şablon dirêjtir bin, em karin wekî ya wîkîya almanî bikin (ew jî wekî teklîfa te ye). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:50, 15 tîrmeh 2026 (UTC)
::::Em tabloya lêkerê jî rakin? 1)Em wekî navdêran bikin, bin beşa Tewandinê 2) Tabloya {{ş|ku-tewîn-reh}} were şûnda tabloya {{ş|ku-tewîn-lk}}: '''[//ku.wiktionary.org/w/index.php?title=gotin&diff=prev&oldid=6323518]''' , '''[//ku.wiktionary.org/w/index.php?title=vexwarin&diff=prev&oldid=6323525]''' 3) Bi şablona {{ş|tewandin}} û bi parametreyên {{param2|niha}} û {{param2|borî}}: '''[//ku.wiktionary.org/w/index.php?title=vexwarin&diff=6323528&oldid=6323525]'''--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 10:46, 3 tebax 2026 (UTC)
:::::Ez dibêjim em {{ş|ku-tewîn-lk}} bixin bin beşa Tewandinê feqat dema niha û borî tevî formên wan ên negatîv tê de cih bigire, hinek fireh be jî baş e. Dilê min dixwaze yek ji opsiyonên 2 û 3 tevî beşa Tewandinê jî bi kar bînin lê nizanim kîjan hilbijêrin? Herwiha ji bo îsman em dikarin resim zêde bikin ji bo nîşandanê, em ê fiîlan çawa bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:10, 3 tebax 2026 (UTC)
::::::Bi min ji bo kesên ku kurmancî dizanin, tabloya tewandinê "piranî" ne hewce ye. Dema ku ez lêkereke nû hu dikim pêşta rehê wê digerim paşê jî devoka wê... Şablonana "ku-tewîn-reh" pirr dirêj nîne: li ser mobîlê jî pirsgirêk nîne: bin beşa "Lêker"ê de tê nivîsandin: lojîk e. Problema beşa Tewandinê va ye: em ê hertim du rûpel serrast bikinê, rupela lêker (beşa "Tewandinê") û rûpela Tewandin:''lêker''. Bila hêmû gotûbêj tenê li ser rûpela Tewandinê were kirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:06, 3 tebax 2026 (UTC)
:::::::Erê em ku-tewîn-lk bi ku-tewîn-reh biguherînin dê baştir bibe. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:30, 4 tebax 2026 (UTC)
== Reşwî ==
Silav ji we gî re, Pirsekê min heye.
Ez difikirim şablonekê din çê bikim boy devokek kurmancî, yê êla Reşwî. Ew lêkerên xwe hinek cuda ditewînin. Mesela te hem "ez dikim" bêjî yan jî "ez dikime", "em dikinî, em tênî, te em kuştinî" (ne "em dikin") dema xwe yek in lê maneyên xwe ne yek in, "ez bikim" "ez ê bikim" "ez bikime" û dewamên xwe hemû demên xwe yek in lê mane dîsa ne yek in.
Eger min çê kir, şûna xwe li ku derê tînim? Ez difikirim li tewandina devokên kurdî yê ku "tabên" xwe hene "kurmancîya rojavayî", "kurmancîya standard" hsw. Lê tewandin ewqes cuda ye dixwaze ku wî şablona tewandina kurdî pir were guherîn.
Hûn çi dibêjin? (R: Hûn dewên çi?) [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 11:38, 7 tîrmeh 2026 (UTC)
:{{silav|Kurdanparez}} Binêre mînaka devoka ''rojhilatî'': [[Modul:ku-tewandin#L-135]] / [[Modul:ku-tewandin#L-138|L138]] / [[Modul:ku-tewandin#L-143|L-143]], .. Di nav modula ''ku-tewandin"-ê de tê çêkirin. [[Gotûbêja modulê:ku-tewandin#Paşgirên lêkeran di kurmanciya rojhilatî de]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 13:09, 7 tîrmeh 2026 (UTC)
::PS: Dema niha: [[Modul:ku-tewandin#L-128]]; dema borî: [[Modul:ku-tewandin#L-153]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 13:15, 7 tîrmeh 2026 (UTC)
:::Merheba @[[Bikarhêner:Ghybu|Ghybu]], Eger mimkin be tu dikarî yên devokî têxî binmodulekê û bila modul load bike, bi vî awayî belkî çêkirina yên nû û famkirina modulê hêsantir be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:09, 7 tîrmeh 2026 (UTC)
:::Lê di devokê reşwî de hem dema niha yê statîk heye (Îng: I do -> Ez dikim) û yê progressîf heye (I am doing -> Eîz (nêr) Ez î (mê) dikime, yan jî Ez î dikime/Ez ê dikime). Reşwî ji yê standardê pirtir bo zayend ferq tîne, hem jî li dema pêşî (Ez ê bikim (Nêr) ez a bikim (Mê)). Dema pêşî jî formên cuda hene (Ezêk bikime (I will be doing it) û ez ê/a bikim (I will do it))
:::Ev di modûlê têhel bê? [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 22:09, 8 tîrmeh 2026 (UTC)
== WF:Xebat ==
Min sayfeya [[WF:Xebat]] çêkir, xeletiyên ku hûn ferq dikin ji kerema xwe lê zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:35, 15 tîrmeh 2026 (UTC)
== Modul:quote ==
@[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Keyomerd|Keyomerd]], @[[Bikarhêner:Ferhengvan|Ferhengvan]] Merheba, ez modula îngilîzî ya jêgirtin û çavkaniyan li gorî şablonên {{ş|jêder}} format dikim û ji bo ku hertişt wekî formata me ya niha be, gelek serê min diêşe. Ez dibêjim bila em dev ji formata niha berdin wekî ya îngilîzî bi kar bînin ji ber ku gelek xisûsiyetên îngilîzî hene ku yekî dikare biemilîne. Hûn ê bibêjin niha ji kû hat bîra tê, min got [[Wîkîferheng:Xebat#MediaWiki:Gadget-start.js|start.js]] nû bikim û min dît otomatîk çavkanî bi riya [[mw:Citoid]]ê bi riya DOI, ISBN û lînk tê çêkirin, min got wiya jî li skrîptê îlawe bikim loma min [[Wîkîferheng:Xebat#Mediawiki:Gadget-citoid-helper.js|Mediawiki:Gadget-citoid-helper.js]] hazir kir lê hin parametreyên Citoidê kêm bûn, min got ka wana rast bikim, min dît formata me ya niha ti standard bi kar nayîne tenê parametreyan bi virgulê li hev dixe.
Ez dibêjim formata îngilîzî baştir e, hem ne îtalîk e ku bi hêsanî tên xwendin, hem jî tarîx û çavkanî herî pêşî tên nîşandan ku ji bo peyitandinê bo min muhîm e. Modula îngilîzî de hema bêje her tişt hatiye çêkirin, eger em rûnin tenê yên lazim li şablona çavkaniyên xwe binivîsin ew ê gelek wext bistîne. Eger em modula îngilîzî bi kar bînin, em ê hem karibin şablonên îngilîzî bi hêsanî û rast kopî bikin hem jî kurdîkirina parametreyan gelek hêsan e. Fikir? --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:20, 24 tîrmeh 2026 (UTC)
: Ji bo miqayesekirinê bnr. [[:en:Template:quote-book]] / [[Şablon:jêder-kitêb/ceribandin]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:32, 24 tîrmeh 2026 (UTC)
::Bi min bila yê mewcûd jî bimîne, ew jî şiklen xweş û baştir e. Feqet bi min jî yên îngilîzî rehettir in, eger rastkirina yê mewcûd zehmet be, em rasterast wan ji xwe re bînin û tercime bikin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:41, 24 tîrmeh 2026 (UTC)
:::Ez êvarê ve dixebitim, min nêzîk kiribû lê min got belkî hûn qane bibin lê xuya ye hûn ê hîn serê min gelek biêşînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:59, 24 tîrmeh 2026 (UTC)
::::@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Eger îtîrazek tinebe, ez ê îtalîk ji jêgirtinê rakim... Jixwe bi riya [jêgirtin] dikare were veşartin û her daim bi * dest pê dike loma bi hêsanî tê fêmkirin û xwendin rihettir dibe. Bnr. [https://ku.wiktionary.org/w/index.php?title=W%C3%AEk%C3%AEferheng:ceribandin/nim%C3%BBne&oldid=6277132#L%C3%AAker_2 ceribandinê] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:13, 24 tîrmeh 2026 (UTC)
== Request for comment (the future of Abstract Wikipedia) ==
<bdi lang="en" dir="ltr" class="mw-content-ltr">
Apologies if this has not yet been translated into your wiki's language. {{int:Please-translate}}
You are invited to voice your opinions in a [[:m:Requests for comment/The future of Abstract Wikipedia|request for comment about the future of Abstract Wikipedia]]. {{Int:Feedback-thanks-title}}
[[:m:User:Kowal2701|Kowal2701]] ([[:m:User talk:Kowal2701|talk]]) 12:25, 24 tîrmeh 2026 (UTC)
</bdi>
<!-- Message sent by User:DreamRimmer@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== ''mê'' yan ''nêr'' li gorî maneyê / ''mê'' yan ''nêr'' (bikaranîn dudil e) ==
[//ku.wiktionary.org/w/index.php?title=xelat&diff=prev&oldid=6291766]: Mixabin pirr bikarhêner şûnda parametreya <code>z=m/n</code> ('' m yan n li gorî maneyê'') <code>z=mn</code> (''m yan n (bikaranîn dudil e)'') bi kar tînin ([[Şablon:navdêr#Parametre]]) ji ber ku di nav her du nivîsê de pir ferq nebînin. Nizanim em ê dîsa parametreya <code>z=nt</code> (''nêtar'') çêkin, wekî berê: [//ku.wiktionary.org/w/index.php?title=W%C3%AEk%C3%AEferheng:D%C3%AEwan/ar%C5%9F%C3%AEv_2019&oldid=6104681#Zayend]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:17, 28 tîrmeh 2026 (UTC)
:Merheba {{r|Ghybu}} {{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|kelîme|c=mn}}</nowiki>}}
Ya din jî; {{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|kelîme|c=m/n}}</nowiki>}}
Tu dixwazî em wana berevajî bikin? Not: nêtar ji bo kurmancî nabe, mn û m/n baş in. Berê ji ber ku m/n tinebû, her kes li gorî parametreyên heyî dixebitî. Niha hene bi min problem nîne eger em berevajî nekin.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 05:32, 28 tîrmeh 2026 (UTC)
: Bi min ev format baştir e:
{{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|kelîme|c1=m|c1etîk=Mêrdîn|c2=n|c2etîk=Dihok}}</nowiki>}}
Yan jî:
{{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|zayend|c1=n|c2=m|c1ref={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 1 Hejmar 1-23 (1932-1933)/1998|hejmar=4|sal=1938}}|c2ref={{Zana Farqînî}}}}</nowiki>}}
{{çavkanî}}
:--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 05:49, 28 tîrmeh 2026 (UTC)
::Feqat em dikarin li şûna {{param2|c|m/n}}, {{param2|c|mn?}} yan {{param2|c|mn!}} çêkin ji bo ku neyên tevlihevkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 06:00, 28 tîrmeh 2026 (UTC)
:::Ez jî dizanim ku "nêtar ji bo kurmancî nabe" ([//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj%3Afikirmend&diff=3063789&oldid=3063526]) jixwe min "nêtar" kiribû "[//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj:fikirmend&diff=prev&oldid=3063788 mê an nêr li gorî bikaranînê]". Feqet nizanim çima rind nayê fêm kirin: Ferhengvan jî ew parametreyê bi awayekî rast bi kar nayîne [//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj%3Areh&diff=6235991&oldid=6235985]. {{ping|Bikarhêner}} ? Min heta niha pirr gotar serrerastkir... Em ê hertim kontrol bikinê: pêşta problemeke vitira tine bû :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:13, 28 tîrmeh 2026 (UTC)
::::Bi rastî kî li vira parametreyên ku te li jor nivîsandine bi kar tîne {{=D}} ? Ji bîr meke em li ser wîkîferhenga kurdî ne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:39, 28 tîrmeh 2026 (UTC)
:::::Ya muhîm hebûna sîstemekê ye, heta niha ti sîstemeke baş nehatiye çêkirin û ti rê nehatiye dayîn ji bikarhêneran re ku hîn bibin, hêdî hêdî em ê çi ji destê me were, çêkin û hîn bikin. Bi min atolyeyek lazim e em hin tiştan ji bikarhênerên din re hîn bikin. Mîsal min <code><nowiki>{{subst:beralîbike}}</nowiki></code> li wîkîpediyayê çêkir û ji bikarhênerekî re nîşan da çawa tê bikaranîn, wî dest bi bikaranînê kir. Ji ber ku bikarhênerekî mobîlê ye, nikaribû bi tenê serê xwe beralîkin çêke her daîm alîkarî ji kesên din dixwast ku pir carî ti kes nedihat. Însanên me ji aliyê teknîkî ve baş nebin jî, wextê bi riyeke baş îzah dikî, fam dikim şikir ji xwedê re.
:::::Welhasil, [https://ku.wiktionary.org/w/index.php?title=reh&diff=prev&oldid=6291839 reh] çêbû? Eger tu nabêjî nêtar be, min daxwaza te fam nekir. Tu çi dixwazî em çawa bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 28 tîrmeh 2026 (UTC)
::::::Nivîs dîsa "''mê yan nêr li gorî maneyê''" be (kategorî jî) feqet di nav parametreyê de <code>mn</code> neyê nivîsandin. Parametre wekî {{param2|z/c|nt}} bisekiniyana (an jî navekî din "g" an "c") feqet nivîs û kategorî bihata guhertin... Herwiha profîlên bikarhênerên ku tên vira wekî en.wiki nînin: li vir zêde namînin û tiştên ku hu dikin jî ji bîr dikin. PS: Di zimanê peştûyî de jî (nêzîkî zimanê kurmancî ye) peyvên "mê an nêr" (''common gender'') hene [[:en:Category:Pashto nouns with multiple genders]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:28, 28 tîrmeh 2026 (UTC)
:::::::"g", "c", "nt" gî behsa cureyekî xisûsî dikin loma nabin, feqat di nav [https://ku.wiktionary.org/w/index.php?title=Modul:r%C3%AAziman&oldid=6115355#L-306 Modul:rêziman] de koda me "mn" otomatîk dike "mnmane" min ji bo back-compatiblity wisa kiribû lê ya rast "mnmane" ya, em "mn" ji bo kurmancî rakin, hemû "mn" bikin "mnmane" û "m/n" jî rakin û bikin "mndudil", nabe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:44, 28 tîrmeh 2026 (UTC)
::::::::OK, bi min baş e:) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 28 tîrmeh 2026 (UTC)
:::::::::Îmkan heye şablona tewandinê jî wisa bikî ku hem di qismê tewandinê de hem jî aliyê rastê were bikaranîn? Wekî me [[Wîkîferheng:Dîwan/2026#Beşa_Tewandin_bo_kurmancî|li vir]] got? Wextê gelek dirêj be yan mndudil be, em dikarin bixin qismê tewandinê bi şiklê "ya mêr" û "ya nêr". Wekî din yên biçûk û standard bila li aliyê rastê bimînin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:54, 28 tîrmeh 2026 (UTC)
::::::::::Erê bi parametreya {{param2|hêl|çep}} tê çêkirin. Belkî bi JSê tiştekî vitra jî tê çêkirin: dema ku tablo pirr fireh ye (>450px) tablo wekî "veşartî" xuya bike...
::::::::::"''Wekî din yên biçûk û standard bila li aliyê rastê bimînin?''": '''Aliyê rast:''' Dema ku tu mînakan dixwînî bi hêsanî tabloya tewandinê jî seh dikî (pedagojîk e). '''Bin beşa Tewandinê:''' 1) Ji bo mobilan rind e, tablo ne ser maneya peyvê ye. 3) Şûnda maneyan direj dibe 3) Wêne baştir xuya dikin ji ber ku ne bin tabloyê ne. '''Encam''': yek pratîk e, yek jî pedagojîk e [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:27, 28 tîrmeh 2026 (UTC)
:::::::::::Di bin beşa Tewandinê de baştir xuya dike... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:28, 28 tîrmeh 2026 (UTC)
::::::::::::Erê {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:29, 28 tîrmeh 2026 (UTC)
:::::::::::::Feqat lazim e yên mezin wekî [[mustensîx]] di nav qutîyê de bê wekî [[:en:heval]] di mobîlê de ya me sayfeyê fireh dike, ya îngilîzî de dikarî qutîyê bişemitînî [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:44, 28 tîrmeh 2026 (UTC)
::::::::::::::Formata herî xweş [https://ku.wiktionary.org/wiki/mustens%C3%AEx#Kurmanc%C3%AE] resm jî lê tê tablo jî di cihê xwe de ye. Wekî din wextê tablo li aliyê rastê ye însan naxwaze resmekî jî lê zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 28 tîrmeh 2026 (UTC)
:::::::::::::::''overflow'' bi tagên "div"-an rind tên çêkirin. Em ê tabloyên ''NavFrame'' bi kar bînin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 28 tîrmeh 2026 (UTC)
::::::::::::::::Eger class inflection-table bi kar bînî, bi [[MediaWiki:Gadget-defaultVisibilityToggles.js#L-43]] ê ku dixwaze dikare gîya veşêre an nîşan bide. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:11, 28 tîrmeh 2026 (UTC)
:::::::::::::::::Min fêm nekir, biryara we çi ye? Hemû bin beşa Tewandinê? an jî herdu? Li gor wê şablon tê guherandin. Bi min ferq nake, her du jî baş in... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:19, 28 tîrmeh 2026 (UTC)
::::::::::::::::::Me [[#Beşa Tewandin bo kurmancî|li jor]] gotibû bila hemû di beşa Tewandinê de bin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::Erê, min xwand Bikarnêner tiştekî din gotibû... Tu bi botê çêdikî? Bi botê hemû tabloyan bê parmetreya "hêl" bike bin beşa "Tewandin"-ê. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:30, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::Va rabim botekê hazir bikim. Bi qasî min jê fam kir, tam nabêje hemû nekin, nabêje hemû bikin jî. Lê em hemû bikin çunkî du kes got erê. Cihê Tewandinê piştî Bi alfabeyên din û Formên alternatîv be an jî beriya wan piştî maneyê (piştî wajeya klasîk) be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:38, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::Eger di yekcarê de nayê çêkirin bi parametreya "hêl" ve çêke. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:42, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::[https://linkcount.toolforge.org/?project=ku.wiktionary.org&page=%C5%9Eablon%3Aku-tew%C3%AEn-nav 27 hezar] car tê bikaranîn, Eger behsa wextê dikî, ne problem e, eger behsa "tenê yên fireh bixin tewandinê" wê carê yên mê û nêr bixim? Te cewaba pirsê cihê tewandinê jî neda? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:48, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::::Lojîk dibêje ku piştî bi "alfabeyên din", "formên alternatîv" û "Ji wêjeya klasîk" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:55, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::::Feqet tabloya tewandinê dûr dikeve... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:00, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::::::Bi min "Bi alfabeyên din" bixin piştî Bilêvkirinê çûnkî @[[Bikarhêner:Ferhengvan|Ferhengvan]] pir caran ji bo nîşandana dengî alfaba erebî bi kar tîne, h3 rengê teal tenê ji bo "Navdêr, Lêker hwd." bi cssê bi kar bînin [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.css&diff=prev&oldid=6241199 wekî wisa lê berevajî], Piştî "Ji wêjeyê/klasîk" em tewandinê deynîn, "Formên alternatîv" piştî Tewandinê be û îro nekin, eger dixwazî di ceribandinê de tabloya nû hazir bike, em hefteyek li hêviya hevalên din bisekinin, eger îtîraz nehat, em çêkin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:15, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::::::Beşa "Bikaranîn" jî heye... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:28, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::::::::Piştî Bikaranînê bnr. [[:en:Wiktionary:Entry_layout#Headings_after_the_definitions]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:37, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::::::::(h3) Bilêvkirin, Bi alfabeyên din, Sinifa rêzimanî,
::::::::::::::::::::::::::::(h4) Ji wêjeyê/klasîk (divê were xistin bin maneyê), Bikaranîn, Tewandin, Formên alternatîv, Hevmane, Nêzîk, Dijmane, Jê, Têkildar, Etîmolojî (eger du sinifa rêzimanî ji eynî kokê hebe h3) Dûnde, Binêre herwiha, Werger [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:40, 28 tîrmeh 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}} {{r|Ferhengvan|Bikarhêner|Keyomerd|Zerwanda|Kurdanparez}} Fikir? --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:42, 28 tîrmeh 2026 (UTC)
:"Bi alfabeyên din" beşeke pirr hewce nîne, niha beşa "mane" li ser rûpelê dûr dikeve. Bi min "Bîlêvkirin" > Mane > Ji wêjeya klasîk (wekî mînak e) > Bikaranîn > Tewandin > Bi alfabeyên din > Formên alternatîv > ... Divê beşên ku herî zêde tên xwastin werin "ronîkirin". [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 03:07, 29 tîrmeh 2026 (UTC)
::''[[bileks|Bîl'eks]],'' "Bi alfabeyên din" gelek muhîm e, ev ji bo kurmancî "formên alternatîv"-a wîkiya îngilîzî ye, başûrî tenê bi alfaba erebî dinivîsin û wextê kelîmeyek digerin çawa zanibin hatin kîjan kelîmeyê? Bi kirîlî ne lazim e lê bi erebî mecbûrî ye. Ji bo nettir bikin, lazim e em beşa "Bi alfabeyên din" baştir bikin. Mane hinek li jêr be ne muhîm e, wîkiya îngilîzî de jî gelek car lazim e herî jêr ji bo dîtina maneyê. "Ronîkirin"/teal xelet tê famkirin, sankî "qismê sereke (h3) lazim e wê be û qismên din werin xistin binê wê" tê famkirin loma em tenê Mane ronî bikin bes e, ka [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6291927 vê cssê] biceribîne û dîsa fikrên xwe bibêje. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 07:20, 29 tîrmeh 2026 (UTC)
:1. '''Li ser cinsîyetê:''' Bi min, em malûmatên ekstra yên wek ''"li Dihokê nêr, li Serhedê mê"'' (Serhed:m|Dihok:n) li qismê ''"Bikaranîn/Emilandin"''ê bidin baştir e. Kelîme di ferhengan de wek form û cinsîyet bi halê standard cih digirin, eger li mintiqayek cuda be ew cudahî li qismê "Bikaranîn/Emilandin"ê wek malûmatek ekstra cih bigire çêtir e. Ferhenga ku em vê gavê ji bo standardkirinê esas digirin ''"Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû"'' de jî bi yek cinsîyetê cih dane kelîmeyan. Ji xeynî wê di ferhenga standardkirinê ya Weqfa Mezopotamyayê de jî ew kelîme bi yek cinsîyetê cih digirin. Tiştekî din jî bêjim, eger em şablonê wek "mndudil" bi nav bikin mimkin e ku rê bide şaşîyan. Çimkî gava mirov wê dibîne, mirov dibêje qey li ser cinsîyeta kelîmeyê dudilîyek heye, bi qenaeta min dudilîyek bi wî şiklî nîne. Divê her kelîme xwedî cinsîyetekê bin û îstîsnayên mintiqayî -ger were xwestin- ekstra were nîşandan. Lewma bi min halê mewcûd baş e. 2. '''Li ser şablonên tewandinê:''' Ger şablonên tewandinê di xuyabûna mobîl de bibin sedema xirabîyê, em wan şablonan ji bo mobîlan têxin qismê "Tewandin"ê. 3. '''Li ser rêzkirina qisman:''' Bi min divê mane li pêş be û ne hewce ye ku qismê "Bi alfabeyên din" derxin pêş. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:20, 29 tîrmeh 2026 (UTC)
::Li ser cinsiyetê erê, eger di ferhenga rastnivîsînê de cinsiyet hatibe nivîsandin ne hewce ye em "mndudil" binivîsin li qismê Bikaranînê behskirin bes e, lê eger tê de ev kelîme tinebe û di ferhengên din de jî dudiliyek li ser bikaranîna cinsiyetê hebe, wê demê "mndudil" lazim e bikaranîn çunkî wîkîferheng lazim e li gorî çavkaniyan were nivîsin. Li ser tewandinê ne mimkûnê em di mobîlê de di qismê Tewandinê de, di sermaseyê de em li qismê maneyê bixin loma lazim e em bi yek şiklî li qismê Tewandinê hemû bixin çêtir e. Li ser "Bi alfabeyên din" ji bo min zêde muhîm nîne, feqat dîsa jî ez dibêjim lazim e were pêşxistin. Tiştekî wisa jî heye eger di sayfeyekê de ji yekî zêdetir sinifên rêzimanî hebe wê demê lazim e Bi alfabeyên din bi sê = were bikaranîn, û cihê wê ne li dawiyê lê divê piştî Bilêvkirinê be.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:45, 29 tîrmeh 2026 (UTC)
:Bi a min beşa çevandinê li serê rûpelê be beşa maneyê pir dûrî çavan dikeve. Li jêrtir be çêtir e. [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 06:01, 31 tîrmeh 2026 (UTC)
::{{r|Wikihez}} Bi min di nav tabloyê de parametreya yekem jî were rakirin, ne hewce ye bi <nowiki>{{PAGNAME}}</nowiki> tê çêkirin. Di rûpelên din de jî (ne yên sereke) bi parametreya {{param2|nav}} were nivîsandin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 11:07, 31 tîrmeh 2026 (UTC)
:::Erê, maqûl e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:23, 31 tîrmeh 2026 (UTC)
::::cinsiyet jî m, n, mn be bes e ma bila wekî {{ş|navdêr}} be. cuda nebin [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:41, 31 tîrmeh 2026 (UTC)
:::::@[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Keyomerd|Keyomerd]], @[[Bikarhêner:Ghybu|Ghybu]] "Bikaranîn" an "Notên bikaranînê" ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:47, 31 tîrmeh 2026 (UTC)
:::::Wekî {{r|Bikarhêner}}î gotî zayenda peyvnan gorî devokan jî diguhere, divê ew jî bêne dîyarkirin. Herwiha beşa lêvandinê a ji bo pîtên cêwî pir tê paşguhkirin. Divê jê re cîyekî cihê bê hilanîn. Û ew jî wek zayendê bi devokan diguhere. Meselen tya pêşîn a "tişt" ê li ba me nerm e lê li deverên din hişk e. Û ji devla reqê (wek t-ya req) hişk bi kar bê çêtir e, jixwe her kes wê bi kar tîne nizanim req ji ku derketîye. [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 14:05, 31 tîrmeh 2026 (UTC)
:
<syntaxhighlight lang="json">
"subsections": [ "Ji wêjeya klasîk", "Ji wêjeyê", "Bikaranîn", "Tewandin", "Bi alfabeyên din", "Formên alternatîv", "Kurtkirin", "Sembol", "Navê zanistî", "Hevmane", "Dijmane", "Jê", "Nêzîk", "Jornav", "Jêrnav", "Têkildar", "Etîmolojî", "Dûnde", "Binêre herwiha", "Bi zaravayên din", "Werger", "Qafiye", "Anagram" ]
</syntaxhighlight>
{{r|Keyomerd|Ghybu|Bikarhêner}}Li gorî nîqaşa jor û [[Alîkarî:Formata nivîsînê]] min ev lîste hazir kir lê tê de hin kêmasî hene, "Bide ber" tê de tine ye, li kû derê bixim? Mihtemelen beşên din jî hene ku haya min ji wan nîne. Pirsek din, beşên nestandard wekî [https://ku.wiktionary.org/w/index.php?title=berf&diff=prev&oldid=6292461] çi bikim? Min got li dawiyê bixim berî Wergerê lê sankî piştî Wergerê be çêtir e?--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:24, 31 tîrmeh 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Keyomerd|Keyomerd]] Eger îtîraz an fikrekî din neyê, ez ê beşên nestandard bixim beriya "Bi zaravayên din" eger ev beş tinebe, beriya "Werger". Ez ê "Nêzîk" jî bixim piştî Hevmane û li ser [[Alîkarî:Formata nivîsînê]] "rast" bikim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 1 tebax 2026 (UTC)
::Ez ê "Bikaranîn" jî bikim "Notên bikaranînê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:03, 1 tebax 2026 (UTC)
:::Jixwe bot hema bêje hazir e, eger dûre kesek rabe fikrekî din li pêş bixe, em dikarin biguherînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:04, 1 tebax 2026 (UTC)
::::Ev hinek zêdetir problematîk e feqat ez ê "Ji wêjeyê/a klasîk" jî bikim "Ji edebiyatê"/"Ji edebiyata klasîk". Tabî eger fikrekî din hebe, ne problem e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:05, 1 tebax 2026 (UTC)
:::::Na, bi min "wêje" bimînî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 1 tebax 2026 (UTC)
::::::Ok {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:23, 1 tebax 2026 (UTC)
:::::::"Bide ber" pêşiya "Binêre herwiha"
:::::::"Jê" pêşiya "Têkildar" piştî jornav, jêrnav [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:41, 1 tebax 2026 (UTC)
:::::Ji bo min "'''edebîyat'''" çêtir e, bila ne wêje be. "wêje" kelîmeyeke şaşçêkirî ye. Mahîyeta qisman (kîjan qism bi kêrî çi tê) ji navên wan nayê famkirin. "Binêre herwiha" û "Herwiha" tevlihev dibû, baş e we rast kirîye. Feqet mahîyeta wê qismê ("Binêre herwiha") hê jî ji navê wî nayê famkirin. Jixwe ger em bixwazin bi wî şeklî binivîsin jî divê em bikin "Herwiha binêre", ew jî zid hatîye nivîsandin. Ji xeynî wê, "Navê zanistî" na, "'''Navê îlmî'''" çêtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:51, 2 tebax 2026 (UTC)
::::::Bi min beşa Navê zanistî werin rakirin, wekî en.wikt em li dawiyê yan li pêşiya maneya bi şablona {{ş|q}} binivîsin yan jî wê şablona îngilîzî çêkin ku enwikt bi kar tîne.
::::::Li wikipediyayê min çend car dîtiye Penaber49 jî "Binêre herwiha" dike "Binêre" loma guhartina wê wekî "Herwiha binêre" fikrekî maqûl e.
::::::Herwiha tu ji bo Bêhtir xwendin çi dibêjî [[Special:PermanentLink/6323554#Binêrê_herwiha]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:29, 3 tebax 2026 (UTC)
:::::::Ez dibêjim bila '''''"Xwendina Zêdetir"''''' be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 05:03, 6 tebax 2026 (UTC)
:::Em çima peyva notê bi kar tînin, ma "nîşeyên bikaranînê" ne çêtir e? [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 06:04, 1 tebax 2026 (UTC)
:Rêzkirina dawî:
<syntaxhighlight lang="json">
"subsections": ["Ji wêjeya klasîk", "Ji wêjeyê", "Tewandin", "Notên bikaranînê", "Bi alfabeyên din", "Formên alternatîv", "Kurtkirin", "Sembol", "Navê zanistî", "Hevmane", "Nêzîk", "Dijmane", "Jornav", "Jêrnav", "Jê", "Têkildar", "Etîmolojî", "Dûnde", "Bide ber", "Binêre herwiha", "Bi zaravayên din", "Werger", "Qafiye", "Anagram"]
</syntaxhighlight>
Bnr. [[Wîkîferheng:Bot/Balyozbot/tercîh.json]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:44, 1 tebax 2026 (UTC)
:Bi botê ez ê tenê binebeşên sinifên rêzimanî yên bi 4 = biguherînim, yên din lazim e bi destî werin guhartin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:46, 1 tebax 2026 (UTC)
::@[[Bikarhêner:Ghybu|Ghybu]] Bota rêzkirina binebeşan û cihguhartina Tewandinê hema bêje hazir e. Param {{param|hêl}} bi kar bînim (eger wisa be, em kategoriya "ku-tewîn-nav bê param hêl" çêkin daku hemû xilas bikin) yan jî şablonên heyî bikim ku-tewîn-nav/kevn û piştî kontrolkirinê hemû bikin nû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:26, 1 tebax 2026 (UTC)
:::Bi parametreyê {{param2|hêl}} tu ê paşê wan dîsa rakî... Herwiha min va [https://ku.wiktionary.org/w/index.php?title=abes%C3%AEv&curid=43641&diff=6294632&oldid=6244736] fêm nekir, me çima Tabloya tewandinê rakir :)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:26, 1 tebax 2026 (UTC)
::::Ji bo ku rê bidin tiştên wisa :D Em ê li kû derê deynîn examples?? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 1 tebax 2026 (UTC)
:::::Tablo hazir bin, em dest pê bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 1 tebax 2026 (UTC)
::::::Sernav tê rakirin, mê bikim "m"? yan bila bimînî? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 1 tebax 2026 (UTC)
:::::::Tablo hatiye çêkirin bi [https://ku.wiktionary.org/wiki/Modul:ku-tew%C3%AEn-nav/ceribandin vê] kodê her du parametre jî tên bikaranîn mê/m, nêr/n û mê û nêr/mn... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:41, 1 tebax 2026 (UTC)
:::::Ez nizanim çi bibêjim... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:43, 1 tebax 2026 (UTC)
::::::[https://ku.wiktionary.org/w/index.php?title=abes%C3%AEv&diff=prev&oldid=6294657] ok?
::::::Bi min li şûna {{param|hêl}} em {{ş|ku-tewîn-nav/nû}} bi [[Modul:ku-tewîn-nav/ceribandin|modula ceribandinê]] çêkin, paşê navê şablonê biguherînin baştir be, çunkî [[Special:PermanentLink/6294527#L-337|modula nû]] ne backward compatible e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:56, 1 tebax 2026 (UTC)
:::::::Em ê dîsa paşê nivîsa "/nû" rakin. Bi botê 22000 rûpel di 2 rojan de tê guherandin? Eger wisa ye (bi min pirr nîne), bi navê rast biguhrîne, bila di nav rûpelên ku nehatine guherandin xeletî hebin... Dîsa jî tu dizanî, bot te çêkiriye û tu sererast dikî :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:17, 1 tebax 2026 (UTC)
::::::::Min gelek caran dîtiye, bi botê tiştekî hatiye guhartin lê ji ber ku rind nehatiye kontrolkirin, şablona kevn bi halê xwe yê kevn maye. Tirsa min ew e. Ji ber ku formata hemû sayfeyan ne eynî ne, ez dibêm bot gelek ji wan derbas bike. Bi vî awayî em ê {{ş|ku-tewîn-nav}} sêwî bihêlin û piştî bot xilas kir, em ê bi dil rehetî karibin şablona nû rakin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:26, 1 tebax 2026 (UTC)
:::::::::Rast e! {{param|g|2}} :)) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:32, 1 tebax 2026 (UTC)
::::::::::Mîsalên wan gelek in :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:36, 1 tebax 2026 (UTC)
:::::::::::Eger karibî hinek round edges bixî û rengê background a "Tewandina X" biguherînî, gelek moderntir û xweştir xuya bike :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:25, 1 tebax 2026 (UTC)
::::::::::::''Simplicity is best'' / ''Fewer choices mean less worry:'' felsefeya min e:) Me tabloyê wekî en.wîkî kir [https://ku.wiktionary.org/wiki/%C5%9Eablon:tabloya-tewandin%C3%AA-ser {tabloya-tewandinê-ser}], tenê [//ku.wiktionary.org/wiki/Modul:ku-tew%C3%AEn-nav/ceribandin#L-362 reng] ({{param2|palette}}) tê guherandin (felsefeya wan e jî...). Bi min tiştekî pir rind çêkirine. Dîsa jî em dikarin (wekî berî) bi CSSê biguherînin. Ji bo çêkirina tabloyan min di rûpela şablonê de tiştên ku pirr hewce ne nivisandiye ji bo detayan jî rûpela en.wiki heye. Herwiha tabloyê li gor daxwaza we biguherînin û serrast bikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 03:04, 2 tebax 2026 (UTC)
:::::::::::::PS: Şiklê tabloyê li vira tê guherandin [[Şablon:tewandin-ser/style.css]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 04:41, 2 tebax 2026 (UTC)
::::::::::::::Mala te ava tu ji heqê rengan gelek xweş derdikevî :)
::::::::::::::Ev felsefeya te gelek caran karê min pir zor dike {{=D}} en.witk gelek tiştî bi awayekî xweş çêkirine, ez jî heta ku wana adapte bikim, gelek êş dikşînim bnr. [[#Modul:quote]] û Şablonên etîmolojiyê [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:47, 2 tebax 2026 (UTC)
:::::::::::::::Bot xilas kiriye, Baş e [[Special:PermanentLink/6323268#Şablona_ku-tewîn-nav_ne_li_binê_Serenav_an_Navdêrê_ye|zêde]] xeletî derneketin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 2 tebax 2026 (UTC)
::::::::::::::::Gelek spas! [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:42, 2 tebax 2026 (UTC)
:::::::::::::::::Niha şablon sêwî ma ji xeynî gotûbêjan, ew jî dûrê rast bikim, tu dikarî modulan rojane bikî? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:28, 3 tebax 2026 (UTC)
::::::::::::::::::Spas. Ev rûpel pirr dirêj bû... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:33, 3 tebax 2026 (UTC)
:::::::::::::::::::Erê, rêzkirina beşan ez ê bikim yek satir, lê dîsa jî zêde fêde nede, fikrekî din heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:38, 3 tebax 2026 (UTC)
::::::::::::::::::::@[[Bikarhêner:Ghybu|Ghybu]] [https://ku.wiktionary.org/wiki/W%C3%AEk%C3%AEferheng:D%C3%AEwan#c-Wikihez-20260801005000-Wikihez-20260731162400] û [https://ku.wiktionary.org/w/index.php?title=gur&curid=5384&diff=6328092&oldid=6328089] ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:00, 7 tebax 2026 (UTC)
== SEO ==
Merheba @[[Bikarhêner:Ghybu|Ghybu]] Wextê ''jiyannas'' bi şiklê "jiyannas" lê digerî di nav netîceyên googlê de [[jiyannas]] derdikeve? Min hem bi Chromeê û hem jî bi VPNê ceriband lê [[jiyannaseke]] derdikeve feqat [[jiyannas]] dernakeve. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:31, 2 tebax 2026 (UTC)
:"zengilok" derdikeve [[zengilok]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:38, 2 tebax 2026 (UTC)
:Min jî tenê [https://usercontent.one/wp/portal.arsivakurd.org/wp-content/uploads/2024/08/Bernamegeh-4.pdf?media=1709107483 va] (''Berna Megeh'', Jan Dost, OCCO MAHABAD) û https://portal.arsivakurd.org/wp-content/uploads/2022/06/Xwebun-34.pdf va] (''Xwebûn'', hj.34) dît herwiha ''Jihannasî'' jî heye... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:44, 2 tebax 2026 (UTC)
::Behsa çavkaniyan nakim behsa SEOyê dikim, çima [[jiyannas]] di netîceyan de dernakeve. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:49, 2 tebax 2026 (UTC)
:::Min fêm nekir, pirsa te çima Wîkîferheng dernakeve ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:51, 2 tebax 2026 (UTC)
::::Erê çima sayfeya Wîkîferhengê ya [[jiyannas]] dernakeve feqat [[jiyannaseke]] derdikeve. Min du nimûne nivîsandin ka piştî mehekê em binêrin derkeve yan na. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 2 tebax 2026 (UTC)
:::::Bi min dema ku motora googlê "jiyannaseke" (-eke bi kurdî ye) digere, ew peyv tenê di kurdî de heye ji ber vê yekî dide. Feqet "Jiyannas" di nav zimanên din de jî heye (belkî difikire ku ew xeletî ye.). Herwiha nivîsa bin "Jiyannas" li gor "jiyannaseke" biçûk e û li bin "biolog" hatiye nivîsandin:) Herwiya peyva "biolog" jî dide ew jî giridayî pirr peyvan e... Pirr parametre hene... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:09, 2 tebax 2026 (UTC)
::::::Wextê li "birakujî" dictionnaire digerî netîceya ewil rasterast fr.wikt e. Wana tiştekî kirine lê çi? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:13, 2 tebax 2026 (UTC)
:::::::"Trust Score" ? Herwiha peyva "birakujî" pêş Wîkîfehengê hatiye çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:21, 2 tebax 2026 (UTC)
::::::::Nizanim [[:en:Wiktionary:Votes/2023-08/Installing the WikiSEO extension]] heye lê [[:phab:T346590]] sekinî ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:27, 2 tebax 2026 (UTC)
:::::::::Erê, feqet lînkên û navên Wîkîferhenga fransî û îngilîzî di internetê de pirr tên parvê kirin. Wîkîferhenga fansî ferhengek derxistiye: [https://www.fnac.com/a13530183/Collectif-Le-dico-adapte-du-Wiktionnaire Le dico] (li ser Amazonê). Kî qala Wîkîferhenga kurdî dike? Ji ber vê yêke hertim pêş me ne. Daxwaza wan werin pêş malperên din... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:36, 2 tebax 2026 (UTC)
::::::::::[https://www.google.com/search?sca_esv=a854c327b7bb0786&q=%22W%C3%AEk%C3%AEferheng%22&tbm=nws&source=lnms&fbs=ABfTbFUDadgeu2mn4mYJ8iEZ1GUDDuwRa4um-2LNhBFWIpqZc7ZL_t3iAe0lB1D4hb_Ohp7yg8FO-XwZ7R-ba4YefvIQCF4Ra4wbUNQStQ8dVc6IK2vWHoXbgHLwJobq4HTNMksBhYHBmxw2hK9Sa4pHAI3I2Ovzox54EiEId7q7IoMJRkRlr-XDezqDqx33xDsZIWuHy-a_vqoqDlYrhNBbNeVEP9MwaA&sa=X&ved=2ahUKEwiz94Co9IKWAxXNUaQEHQJAAGYQ0pQJegQIGBAB&biw=1320&bih=583&dpr=1 Wîkîferheng] {{=D}} Li ser X û medyayên din de jî pirr qala Wîkîferhengê nakin... Bi rastî kî Wîkîfrhengê dixwîne? [https://pageviews.wmcloud.org/topviews/?project=ku.wiktionary.org&platform=all-access&date=yesterday&excludes=]... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:41, 2 tebax 2026 (UTC)
:::::::::::Haya ti kesî ji Wîkîferhengê nîne bi rastî loma pêşxistina SEOyê hewce ye. Ez dibêjim miheqeq fr.wikt tiştek çêkiriye ne mimkun e wextê heval dictionnaire binivîsî wiktionary derkeve bêyî çêkirina tiştekî. Not: Min ferq kir li ser fr.wikt wextê bi mobîlê tê dikevî, eger di sayfeyekê de du ziman hebe ya fransî otomatîk vedibe zimanê din veşartî dimîne. Fikrekî xweş e em jî çêkin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 2 tebax 2026 (UTC)
::::::::::::Erê bila were çêkirin. Min gotûbêja wan li ser vê mijarê xwandibû, Wîkîferhenga fransî jî ne razî ye. Dema ku li ser Googlê tu peyvek diguherî Wîkîferhenga wan di bin Wîkîpediyayê û di bin malperên din de derdikeve. Û çend caran şûnda "Wiktionnaire" "Wikipédia" tê nivîsansin... Ew jî nizanin çi bikin :)) Niha dizanim ku en.wikt ji ne razî ye... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:37, 2 tebax 2026 (UTC)
:::::::::::::@[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Wikihez|Wikihez]]: Ez dibêjim rûpelên ku tenê form di wan de hene, divê <nowiki>__NOINDEX__</nowiki> bin. Wî çaxî, rêza wan nikare ji lemmayê bilindtir be ji ber ku wê nînbin. Ez dibêjim belkî îhtîmala derketina lemmayê bêhtir jî bibe heke netîceyên ku dişibin wê nînbin. Jixwe lemma wê derkeve heke yek li formekê bigere, ji ber ku di rûpelê lemmayê de tabloyên tewandinê hene û ev tên jimartin. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 22:55, 2 tebax 2026 (UTC)
::::::::::::::@[[Bikarhêner:Guherto|Guherto]] Em dibêjin tenê form xuya dike ya sermade xuya nake tu dibêjî em formê noindex bikin {{=D}}
::::::::::::::Claude [[Special:PermanentLink/6323375|li vir]] dibêje ya ku Ghybu got rast e, ji ber ku frwikt gelek tê bikaranîn bêtir dikeve ber çavê google. Eger rojek Wikiseo li en.wikt were çêkirin, em jî bixwazin qet nebe kêrî tiştekî were.
::::::::::::::Em şablonek ji bo metadescription çêkin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:09, 2 tebax 2026 (UTC)
:::::::::::::::@[[Bikarhêner:Wikihez|Wikihez]] Min got, ''Wî çaxî, rêza wan nikare ji lemmayê bilindtir be ji ber ku wê nînbin. Ez dibêjim belkî îhtîmala derketina lemmayê bêhtir jî bibe heke netîceyên ku dişibin wê nînbin.'' [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 23:10, 2 tebax 2026 (UTC)
:::::::::::Em jegirtinan lînk bikin? Bi vî sayî gelek sayfe lînk bide ser hev. Bi riya cssê em dikarin rengê tarî bikin da ku wekî lînk xuya nekin... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:39, 3 tebax 2026 (UTC)
::::::::::::Wextê jîwar ferheng yan jî boçik ferheng lê digerî rasterast Wîkîferheng dertê. Meselê hejmara bikaranîna eynî kelîmeyê di sayfeyê de ye. Em çiqas jêgirtin lê zêde bikin ewqas baştir dibe netîce :) Google AI Mode rasterast Wîkîferheng wek çavkanî nîşan û lînk dide loma kurmanc bi kar neyînin jî AI bi kar tînin baş e ... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:24, 3 tebax 2026 (UTC)
:::::::::::::Êêêê Google AI Overview çi bi şikle "tarîxiyen" ferheng lê digerim wekî ku min li ferhengê qeyd kiriye gelek xweş îzah dike (ji ber ku hîn gelek nû ye netîceyan de Wîkîferheng dernakeve) Nimûneyên bikaranînê jî ji înternetê derdixe. Çi mana ferhengê nemaye... Em çi îşî dikin li vir :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:47, 3 tebax 2026 (UTC)
:::::::::::::Êdî Google jî nayê bikaranîn :)) Niha kî tere ser ''Stack Overflow'' û di nav belgeyên kodan de digere... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:47, 3 tebax 2026 (UTC)
::::::::::::::Erê welle, ez gelek car dibînim kesên derdora min hemen Gemini an Chatgpt vedikin, çi tiştê biçûk hebin, hemen pirs dikin, qet li ser Googlê jî lê nagerin :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:52, 3 tebax 2026 (UTC)
:::::::::::::::"êgsig ferheng" netîce nade feqat wexta "ornag ferheng" digerim rasterast wîkîferheng derdikeve li binê dibêje "Tewandina ornag. Zayenda nêr a binavkirî. Rewş, Yekjimar, Pirjimar. Navkî, ornag, ornag. Îzafe, ornagê · ornagên. Çemandî, ornagî · ornagan.Read more"
:::::::::::::::Meraq dikim guhartina cihê tewandinê çi netîce bide. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 3 tebax 2026 (UTC)
::::::::::::::::Pêşta me tabloya Tewandinê vitira dinivîsand: [[ketin (Tewandin)]] Dma ku nav hat guherandin li ser Googlê tewandin winda bû...
::::::::::::::::Min tewandin:kirin geriya tabloya tewandina Wîkîferhengê dide. Êdî kes nê vira:) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:38, 3 tebax 2026 (UTC)
:::::::::::::::::Tenê dema "niha" nivîsand. Min jî bi kurdî "dema bê jî binivîse" nivîsî: ew jî nivîsand... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:42, 3 tebax 2026 (UTC)
::::::::::::::::::[https://support.google.com/webmasters/answer/16908024?hl=en tê asteng kirin]... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 02:07, 4 tebax 2026 (UTC)
:::::::::::::::::::kî dibêje? li wir tiştek wisa nabêje...? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 02:16, 4 tebax 2026 (UTC)
::::::::::::::::::::Va hatiye nivîsandin: "''Exclude my site's links and content from Search generative AI features: Your site's content is prevented from being visible to users in Search generative AI features, including being linked to within these features and helping with grounding AI responses in these features. You won't receive any traffic or impressions from these features. Learn more about what excluding your site means.''" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 02:23, 4 tebax 2026 (UTC)
== Moda tarî ==
Bi chrome û Firefoxê wextê Reng dikî "otomatîk", eger tarî be, tiştên bi reng xirab xuya dikin, lê wextê "tarî" hildibijêrî xweş xuya dikin. Min hemû js/css/Gadget betal kirin û ?safemode=1 ceriband, dîsa jî wextê "otomatîk" e, her tiştê tarî xirab e. Êdî nizanim problem li cem min e yan di Moda tarî de ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:15, 9 tebax 2026 (UTC)
== Formata jêgirtinan ==
Merheba hevalno {{r|Ghybu|Bikarhêner|Keyomerd|Kurdanparez|Guherto|Şêr}} Wekî min [[#Modul:quote|li jor]] jî gotiye, formata îngilîzî ji ya me baştir e. Her tişt di cihên xwe de ne. Ya me wexta çend tercime yan jî transliterasyon îlawe dikî, gelek tevlihev dibe. Berê me çavkaniyê bi amûra [[MediaWiki:Gadget-jeder.js]] kurt kiribû feqat ew jî ne tiştekî baş e ji ber ku gelek malûmat tên veşartin û kesên nû tên tenê sal û nivîskarê dinivîsin carinan ew jî ne tiştekî baş e. Em dikarin amûra jêderê wisa eyar bikin ku bêyî parentez bixebite û ewilî vekirî be, dûre ê ku dixwaze malûmatan kurttir bike. Eger em vî tiştî bi formata me ya niha bikin, jêgirtinên bi çavkaniyên dirêj dê gelek xirab xuya bikin.
Hûn dikarin li vir miqayese bikin ka kîjan baştir xuya dike:
# [[Special:PermanentLink/6329434|jêgirtina dirêj bi îngilîzî bi formata en.wikt]] vs. [[Special:PermanentLink/6329414|bi formata me ya niha]]
# [[Special:PermanentLink/6329439|jêgirtineke bi soranî bi formata en.wikt]] vs. [[Special:PermanentLink/6329437|jêgirtineke bi soranî bi formata me ya niha]]
Ez naxwazim xwe wisa nîşan bidim ku li ser eynî tiştî her daîm israr dike feqat bi ya min ev zarûriyeteke ji bo baştirkirina xwendina nimûneyan e. Loma ez li vir dengdanek li dar dixim, bila qerar çi be, em wisa bikin, dûre em dikarin amûreke nû çêkin ku li gorî tercîhên bikarhênerî formata en.wikt yan jî formata niha were bikaranîn.
Ji kerema xwe raya xwe li jêr bidin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:01, 11 tebax 2026 (UTC)
=== Dengdan ===
{{dengdan|destpêk=11.08.2026|dawî=18.08.2026}} <!-- kengî dengdan xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
==== Bila formata niha bimîne ====
<!-- Li jêr '# {{erê}} ~~~~' binivîse ji bo raya erê. -->
==== Bila formata en.wikt were bikaranîn ====
<!-- Li jêr '# {{erê}} ~~~~' binivîse ji bo raya erê. -->
# {{erê}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 11 tebax 2026 (UTC)
# {{erê}} [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 14:40, 16 tebax 2026 (UTC)
=== Gotûbêj ===
{{şirove}} Eger hûn bibêjin çima sal û çavkanî li herî pêş e sebeb ev e: Ji bo en.wiktê "attestion" yanî peyitandin bi riya emilandinên heqîqî pêk tê. Yanî eger di ferhengekê de ev kelîme derbas bibe ji bo wan ne muhîm e, ya muhîm emilandina di nav cumleyan de ye. Mîsal ew kelîmeyên ku tenê ferhengan de derbas dibin dixin [[:en:Appendix:English dictionary-only terms]]. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:14, 11 tebax 2026 (UTC)
:@[[Bikarhêner:Ferhengvan|Ferhengvan]], @[[Bikarhêner:Bibliophile|Bibliophile]] û @[[Bikarhêner:Dûrzan|Dûrzan]] fikrê we çi ye li ser vê meseleyê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:48, 12 tebax 2026 (UTC)
::Ez ji tiştên teknîk pirr fêhm nakim. Çewa baş be ên ku pispor in bila biryar bidin... [[Bikarhêner:Dûrzan|Dûrzan]] ([[Gotûbêja bikarhêner:Dûrzan|gotûbêj]]) 21:04, 12 tebax 2026 (UTC)
== Guhartina rêbaznameyê ==
Ez fam nakim me çima di rêbaznameyê de gotiye twitter nayê hesibandin wextê çavkanîdayînê. en.wikt bi kar tîne û eger wek cimleyek derbas bibe bi min ti problem nîne. Fikr? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 14 tebax 2026 (UTC)
:Heta twitter ji bikaranîna ferhengan hîn baştir e, qet nebe em emilandina kelîmekê ya eseh dibînin... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:07, 14 tebax 2026 (UTC)
dvqwp0iw8h29sa780y63qw9j7ps8i5w
6334312
6334311
2026-08-17T12:55:13Z
Wikihez
7344
/* Guhartina rêbaznameyê */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334312
wikitext
text/x-wiki
<noinclude>{{Dîwan/arşîv-ser|2026}}</noinclude>
{{clear}}
== Daxwazên standardkirinê ==
https://ku.wiktionary.org/wiki/W%C3%AEk%C3%AEferheng:Daxwaz%C3%AAn_standardkirin%C3%AA, Gelo di vê malperê de peyvên hatîye standart kirin; ji malperê rakin? Zêde tehlîhev e. @[[Bikarhêner:Bikarhêner|Bikarhêner]]@[[Bikarhêner:Guherto|Guherto]] [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 10:03, 8 kanûna paşîn 2026 (UTC)
:@[[Bikarhêner:Rewiye tine|Rewiye tine]] [[Taybet:Tercîh#mw-prefsection-gadgets]] li vir amûra [[MediaWiki:Gadget-aWa.js/belgekirin|awa]] aktîv bike û pê re dikarî gotûbêjan arşîv bikî. Li sayfa [[WF:DS]] li kêleka her sernavê gotûbêjê <nowiki>[arşîv bike]</nowiki> derdikeve, pê lê bike û ew ê arşîv bike. Eger nikarî tu tenê şablona {{ş|ds-kirî}} îlawe bike, ez ê dûre arşîv bikim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:35, 5 tîrmeh 2026 (UTC)
== Standartkirin ==
Sala borî, min pêşnîyazek kiribû. Halî hazir piştî daxwaza standarkirinê em du heftî disekinin, eger kesek fikrek dîyar neke. Raste rast peyv li gor ferhenga standart a mezopotamyayê tê standart kirin. Ji xwe sazîyek vê karî dike çima em vê barî dikin stuyê xwe. Mesela ez çaxe di wîkîferhengê de peyvek digêrim; standart a peyvê jî li cem xwe kontrol dikim. Eger destûr hebe bi germî dikarim wê malperê standart bikim. Benda bûna du heftîyan gelo ne zehmet e? Duyemîn pêşnîyaza min nîşandana standarta peyvê di malpera de ye. Kesek dema malperan binêre, divê li jora malperan bibîne ku ev peyv standart e. Ev jî hîzmet e. Çimkî nivîskar, wergêr, edîtor, rojnemavan hez dikin bi peyvê standart binivîsin. https://ku.wiktionary.org/wiki/k%C3%AAf li vir wekî mînak min daye nîşan. Lê îfade eger wisa be: Standarda Rastnivîsîna Ferhenga Kurdî a Weqfa Mezopotamyayê kêf (r. 253) e yê çetir be. Çimkî îfadeya berê de peyva kurdî-tirkî derbas dibe û di mejîyê mirov de tevlihevî çêdibe.@[[Bikarhêner:Bikarhêner|Bikarhêner]]@[[Bikarhêner:Guherto|Guherto]] [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 10:25, 8 kanûna paşîn 2026 (UTC)
:Merheba. Hevalno dîyar e karê standarkirinê li wîkîferhengê rawestîya ye. Min sê peyv ji bo standartkirinê qeyd kir. Kes li ser şîrove nenivîsî. Mehek jî li ser derbas bû. Aniha ez dikarim van rupelan biguherim. Li gor qaîdeya hatine danîn wisa ye. Daxwaza min heta niha jî ew e kelîmeyên di ferhenga standartkirinê de heyî divê em bikaribin rasterast li vir jî bikin standart. An jî demê kurttir bikin. Li weqfa mezapotamyayê 20 mirov digêhin hev li ser peyvekî seetan nîqaş dikin. Ma hewceyê em jî li vir nîqaş bikin? Tevlî vê jî ji xwe kes li ser vê mijare nasekîne van dema. Xeynî vê pêşnîyaza min nîşandana standarta peyvê di malpera de hebû. Li ser malperê 'Standarda Rastnivîsîna Ferhenga Kurdî a Weqfa Mezopotamyayê kêf (r.253) e' Tiştek mîna wî hebe yê gelek kêrî her cure nivîskarê me were. Çimkî hin nivîskarên me hez dike bi kelîmeyên standart binivîse. Di malperê de bila vê hêsanî fam bike çêtir e. @[[Bikarhêner:Bikarhêner|Bikarhêner]]@[[Bikarhêner:Ghybu|Ghybu]] [[Bikarhêner:Rewiye tine|Rewiye tine]] ([[Gotûbêja bikarhêner:Rewiye tine|gotûbêj]]) 18:35, 17 sibat 2026 (UTC)
::Merheba @[[Bikarhêner:Rewiye tine|Rewiye tine]]. Ev format pir xirab dibe. Tu dikarî li beşa Çavkaniyê binivîsî bi şablona {{ş|kitêb}} binivîsî çêtir e û tê eynî maneyê. Lê her kes standarda Weqfa Mezopotamyayê qebûl nakin û ez jî şibhê dikim ku ev websîte bi awayekî kolektîv tê çêkirin an na. Loma em bi ya kitêba rastnivisînê çêkin û eger îtîraz tinebe, şablona {{ş|ds-kirî}} li gotûbêja [[WF:DS]] binivîse, dûre li gotûbêjê arşîv bike, ne hewce ye li hêviya kesên din bisekinî. Eger itirazek hebe, ê ku dixwaze dûre dîsa şablona ds zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:30, 5 tîrmeh 2026 (UTC)
:::[https://berwarnews.com/ferhenga-rastnivisine-qonaxeke-giring-a-standardkirina-kurmanci-ye/ Roportaja Mikail Bülbül] li ser proseya standardkirinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:57, 9 tîrmeh 2026 (UTC)
::::Min nû dît an jî nû hatiye zêdekirin lê meqaleyek bi navê [https://www.ferhengkurdi.com/?q=iltihap&modal=lijne lijneya edîtorîyê] nivîsandine [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:40, 22 tîrmeh 2026 (UTC)
== <span lang="en" dir="ltr">Annual review of the Universal Code of Conduct and Enforcement Guidelines</span> ==
<div lang="en" dir="ltr">
<section begin="announcement-content" />
I am writing to you to let you know the annual review period for the Universal Code of Conduct and Enforcement Guidelines is open now. You can make suggestions for changes through 9 February 2026. This is the first step of several to be taken for the annual review. [[m:Special:MyLanguage/Universal Code of Conduct/Annual review/2026|Read more information and find a conversation to join on the UCoC page on Meta]].
The [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee|Universal Code of Conduct Coordinating Committee]] (U4C) is a global group dedicated to providing an equitable and consistent implementation of the UCoC. This annual review was planned and implemented by the U4C. For more information and the responsibilities of the U4C, [[m:Special:MyLanguage/Universal Code of Conduct/Coordinating Committee/Charter|you may review the U4C Charter]].
Please share this information with other members in your community wherever else might be appropriate.
-- In cooperation with the U4C, [[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]])<section end="announcement-content" />
</div>
21:02, 19 kanûna paşîn 2026 (UTC)
<!-- Message sent by User:Keegan (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=29905753 -->
== Request for comment (global AI policy) ==
<bdi lang="en" dir="ltr" class="mw-content-ltr">
Apologies for writing in English. {{int:Please-translate}}
A [[:m:Requests for comment/Artificial intelligence policy|request for comment]] is currently being held to decide on a global AI policy. {{int:Feedback-thanks-title}}
[[Bikarhêner:MediaWiki message delivery|MediaWiki message delivery]] ([[Gotûbêja bikarhêner:MediaWiki message delivery|gotûbêj]]) 00:58, 26 nîsan 2026 (UTC)
</bdi>
<!-- Message sent by User:Codename Noreste@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30424282 -->
== Guharto ==
: <small>→ Ji rûpela ''[//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj:berf%C3%AEtik&oldid=6233338 Gotûbêj:berfîtik]'' hatiye girtin.</small>
Silav {{r|Ghybu}} ma hewce ye em guhartoyan jî kategorîze bikin? Li vê [[ژیشک]] binere, tije guhartoyên wê hene, ku em ji gişan re guhandar binivîsin dê 10 navên heman hebin di nav wê kategorîyê de. Tu çi difikirî? [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 09:40, 26 nîsan 2026 (UTC)
:{{silav|Keyomerd}}. Erê, problem çî ye? {{=)}} Ew jî tên bikaranîn... Dema ku peyvek tê çêkirin hewce ye ku em hemû agahiyên wan binivîsin: bilêvkirin, bikaranîn, mînak, terîm, herêm, tewandin, etîmolojî...
:Bi min peyvên ku wekî ''guherto'' hatine nivîsandin bi nivîsa {{w|Italic type|îtalîk|ziman=en}} (xwahr) di nav kategoriyan de werin nişan kirin (ji bo cudakirinê). {{ping|Ferhengvan|Bikarhêner}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:40, 30 nîsan 2026 (UTC)
::Yên '''''nestandard''''' di kategorîyan de biferq werin nîşandan baştir e. Qene em karibin wan ji yên stadard ferq bikin. @[[Bikarhêner:Ghybu|Ghybu]] Xêra xwe hûn sîstemeke wisa çêkin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 14:36, 1 gulan 2026 (UTC)
:::Merheba @[[Bikarhêner:Keyomerd|Keyomerd]] û @[[Bikarhêner:Bikarhêner|Bikarhêner]] ev xisûsiyet jixwe di şablonên rêzimanê de heye, wextê {{param|formaalt|1}} dinivîsin, made dikeve Kategoriya [[:Kategorî:Formên alternatîv bi kurmancî]] li gorî koda zimanî jî zimanên din tê çêkirin. Hemû madeyên kurmancî bi alfabeya erebî dikevin vê kategoriyê lê nizanim ev tiştekî rast e Yan xelet e, ji bo nîşandanê min çêkiribû lê min hîn wext nedît ku nîqaşek li ser çêkim. Qaîdeyên bikaranîna vê parametreyê di wîkiya îngilîzî de jî tam ne belî ye. Du gotûbêj [https://en.wiktionary.org/wiki/Wiktionary:Requests_for_moves,_mergers_and_splits#Category:Alternative_forms_by_language] [https://en.wiktionary.org/wiki/Wiktionary:Beer_parlour/2024/June#Decluttering_the_altform_mess] çêkirine lê ji bo îngilîzî ev parametre nehatiye bikaranîn. Min ji bo nîşandanê niha [https://ku.wiktionary.org/w/index.php?title=bax%C3%A7%C3%AA_zarokan&diff=prev&oldid=6241372] ceriband lê bi min xelet e. Em çawa bikin? Kengî vê parametreyê bi kar bînin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:29, 8 tîrmeh 2026 (UTC)
::::@[[Bikarhêner:Kurdanparez|Kurdanparez]] Tu çi difikirî? Ji bo zazakî [https://ku.wiktionary.org/w/index.php?title=a_c%C4%B1_geyrayene&oldid=6241396 wisa] bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:55, 8 tîrmeh 2026 (UTC)
:::::Kategorîyekê cuda baş dibe. Wextê ku ez li kategorîya "lekêren bi zazakî" dinêrim, ez hem formê bi şikilê Johnsonê û formê standardê dibînim. Zaten wexte ku ez dikevim ê Johnsonê, min dibe formê standardê. Kategorîyekê cuda, bi taybetî ji bo nivîsandinên alternatîf, baş dibe.
:::::Lê em bîr nekin ku wextê ku em bo formên alternatîf yên devokî qal dikin, belkî adil neye ku em ev bişînin derekê din yê ku însan ewqas nabînin. [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 22:03, 8 tîrmeh 2026 (UTC)
::::::@[[Bikarhêner:Kurdanparez|Kurdanparez]] Gelo min rast fêm kir? Tu dibêjî formên {{q|ku|kîvroşk}} bi dîqet werin guhartokirin lê bi yên bi alfabeya Jacobsonê hemû bikin {{param|formaalt|1}}? Formên Jacobsonê dê bikevin Kategoriya [[:Kategorî:Lêker bi zazakî|Lêker bi zazakî]] û [[:Kategorî:Lema bi zazakî|Lema bi zazakî]] yan na? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:55, 8 tîrmeh 2026 (UTC)
:::::::Bi min naxwaze di wan kategoriyan de bikeve. Wek tu dibînî dûplîkat pir hene. Bi min çareya baştir yên Johnsonê di katehgorîyekê din bikevin (bila format li rûpelan neyin guherandin, bi tenê kategorî) [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 00:37, 9 tîrmeh 2026 (UTC)
== Peyvên soranî bi latînî ([[:Kategorî:Soranî]]) ==
Silav, di nav vê kategoriyê (''[[:Kategorî:Soranî]]'') de pirr peyv tenê bi alfabeya latînî hatine nivîsandin. Divê ew mutleq jî bi alfabeya erebî werin transkrîbe kirin ji ber ku soranî bi alfabeya erebî tê nivîsand... Bikarênerên ku soranî dizanin dikarin wan sererast bikin? Mînakên guhertinan: [//ku.wiktionary.org/w/index.php?title=zor%C3%AEne&curid=6584&diff=6233355&oldid=5859303], [//ku.wiktionary.org/w/index.php?title=s%C3%AAw&diff=6233097&oldid=6233003], [//ku.wiktionary.org/w/index.php?title=sewz&diff=6233313&oldid=6069107], ... Spas! [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:16, 2 gulan 2026 (UTC)
== <span lang="en" dir="ltr">Vote now in the 2026 U4C election</span> ==
<div lang="en" dir="ltr">
<section begin="announcement-content" />
Eligible voters are asked to participate in the 2026 [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee|Universal Code of Conduct Coordinating Committee]] election. More information–including an eligibility check, voting process information, candidate information, and a link to the vote–are available on Meta at the [[m:Special:MyLanguage/Universal_Code_of_Conduct/Coordinating_Committee/Election/2026|2026 Election information page]]. The vote closes on 2 June 2026 at [https://zonestamp.toolforge.org/1780358400 00:00 UTC].
Please vote if your account is eligible. Results will be available by 14 June 2026. -- In cooperation with the U4C,<section end="announcement-content" />
</div>
[[m:User:Keegan (WMF)|Keegan (WMF)]] ([[m:User talk:Keegan (WMF)|talk]]) 17:15, 27 gulan 2026 (UTC)
<!-- Message sent by User:Keegan (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== RFC about AI-generated content in Wikimedia Commons ==
<bdi lang="en" dir="ltr">Apologies for writing in English, please help translate this message to your language. You are invited to participate in a [[c:Commons:Requests for comment/Policy update for AI content|request for comment on Wikimedia Commons about a policy update for AI content]]. This may affect files that are uploaded to Wikimedia Commons for use on this project. Thank you. [[m:User:Codename Noreste|Codename Noreste]] ([[m:User talk:Codename Noreste|gotûbêj]])</bdi> 17:12, 23 hezîran 2026 (UTC)
<!-- Message sent by User:Codename Noreste@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== <span lang="en" dir="ltr">Deployment of Legal and Safety Contacts Link in the Footer of Your Wiki</span> ==
<div lang="en" dir="ltr">
<section begin="Message"/>
'''Legal & Safety Contacts'''
Hello community, the Wikimedia Foundation has provided a [[wmf:Special:MyLanguage/Legal:Wikimedia Foundation Legal and Safety Contact Information|single legal and safety contact page]], to be linked in the footer of your wiki, to ensure access to accurate legal information. This is a regulatory requirement. We have already rolled out links to English, German, Italian, Spanish and other wikis and we will deploy to your wiki soon. [[m:Special:MyLanguage/Wikimedia_Foundation_Legal_and_Safety_Contacts_FAQ|Please read more on the project page]] and leave any comments in this thread or on the [[m:Special:MyLanguage/Talk:Wikimedia Foundation Legal and Safety Contacts FAQ|talk page]].
<section end="Message"/>
</div>
-- [[User:Sannita (WMF)|User:Sannita (WMF)]] ([[User talk:Sannita (WMF)|talk]]) 13:31, 25 hezîran 2026 (UTC)
<!-- Message sent by User:Sannita (WMF)@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=User:Sannita_(WMF)/Mass_sending_test&oldid=30731267 -->
== Jêbirina ferhenga Sadallah ==
Merheba @[[Bikarhêner:Guherto|Guherto]] Îmkan heye lîsteyeke wisa çêkî ku hemû sayfeyên Cyrusbot çêkir yên ku tê de tenê tercimeyeke îngilîzî heye û ev tercime jî hatibe jêbirin? Nimûne ''[[derdzanî]], [[êşzanî]]'' tenê tercimeya [[pestology]] heye ew jî ji ber heqê telîfê hatiye jêbirin. Nimûneyeke din ''zêrrgeh'', ''zêrrkar'' [[goldsmith]] hatiye jêbirin zêr wekî zêrr hatiye guhartin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:48, 26 hezîran 2026 (UTC)
:Temam @[[Bikarhêner:Wikihez|Wikihez]], lê vê heyvê ez nikarim, heyva were ez ê li vê meseleyê vegerim înşellah. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 22:51, 2 tebax 2026 (UTC)
== Beşa Tewandin bo kurmancî ==
Bi ya min şablona {{ş|ku-tewîn-nav}} û ya fiîlê em bixin beşa Tewandinê (wekî [[şodir|zazakî]]. Bi vî awayî hem ji aliyê şiklî ve hem jî ji aliyê bikaranînê ve xweştir xuya bike. Eger di qismê Tewandinê de be, ê ku dixwaze îzah û notên xwe yan jî emilandinên din jî dikare binivîse. Hûn çi dibêjin? @[[Bikarhêner:Ferhengvan|Ferhengvan]], @[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Bikarhêner|Bikarhêner]]. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 6 tîrmeh 2026 (UTC)
:Belkî em dikarin Formata şiweyan wekî ya behdînî jî têxin nav şablonê. Pir caran şiklê tabloya Tercimeyan ji ber dirêjbûna şablona teqînê jî xirab dibe wekî [[astrofizîk]]. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 6 tîrmeh 2026 (UTC)
:Min mijarê te li jorê yê xwe dît, fikirên min jî hene eger baş e. Yê ku te gotiye bi min baş dibe, ji ber ku her rûpel ji vê mîna hev dibe. Em ê rindtir jî ferq li nav devokan binivîsin. [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 11:44, 7 tîrmeh 2026 (UTC)
::@[[Bikarhêner:Bikarhêner|Bikarhêner]] fikr? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 08:44, 15 tîrmeh 2026 (UTC)
:::Formata ku te teklîf kirîye jî ne xerab e, baş tê xuyan. Feqet bi min halê berê (mewcûd) baştir e. Ji ber ku ev şablon (ku-tewîn-nav) bi piranî kurt in û ji bo her kesî ne mihîm e, lewma li kinarê bi formeke hebe ku karibe were vekirin û girtin baştir e. Lê belê ger şablon dirêjtir bin, em karin wekî ya wîkîya almanî bikin (ew jî wekî teklîfa te ye). -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:50, 15 tîrmeh 2026 (UTC)
::::Em tabloya lêkerê jî rakin? 1)Em wekî navdêran bikin, bin beşa Tewandinê 2) Tabloya {{ş|ku-tewîn-reh}} were şûnda tabloya {{ş|ku-tewîn-lk}}: '''[//ku.wiktionary.org/w/index.php?title=gotin&diff=prev&oldid=6323518]''' , '''[//ku.wiktionary.org/w/index.php?title=vexwarin&diff=prev&oldid=6323525]''' 3) Bi şablona {{ş|tewandin}} û bi parametreyên {{param2|niha}} û {{param2|borî}}: '''[//ku.wiktionary.org/w/index.php?title=vexwarin&diff=6323528&oldid=6323525]'''--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 10:46, 3 tebax 2026 (UTC)
:::::Ez dibêjim em {{ş|ku-tewîn-lk}} bixin bin beşa Tewandinê feqat dema niha û borî tevî formên wan ên negatîv tê de cih bigire, hinek fireh be jî baş e. Dilê min dixwaze yek ji opsiyonên 2 û 3 tevî beşa Tewandinê jî bi kar bînin lê nizanim kîjan hilbijêrin? Herwiha ji bo îsman em dikarin resim zêde bikin ji bo nîşandanê, em ê fiîlan çawa bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:10, 3 tebax 2026 (UTC)
::::::Bi min ji bo kesên ku kurmancî dizanin, tabloya tewandinê "piranî" ne hewce ye. Dema ku ez lêkereke nû hu dikim pêşta rehê wê digerim paşê jî devoka wê... Şablonana "ku-tewîn-reh" pirr dirêj nîne: li ser mobîlê jî pirsgirêk nîne: bin beşa "Lêker"ê de tê nivîsandin: lojîk e. Problema beşa Tewandinê va ye: em ê hertim du rûpel serrast bikinê, rupela lêker (beşa "Tewandinê") û rûpela Tewandin:''lêker''. Bila hêmû gotûbêj tenê li ser rûpela Tewandinê were kirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:06, 3 tebax 2026 (UTC)
:::::::Erê em ku-tewîn-lk bi ku-tewîn-reh biguherînin dê baştir bibe. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:30, 4 tebax 2026 (UTC)
== Reşwî ==
Silav ji we gî re, Pirsekê min heye.
Ez difikirim şablonekê din çê bikim boy devokek kurmancî, yê êla Reşwî. Ew lêkerên xwe hinek cuda ditewînin. Mesela te hem "ez dikim" bêjî yan jî "ez dikime", "em dikinî, em tênî, te em kuştinî" (ne "em dikin") dema xwe yek in lê maneyên xwe ne yek in, "ez bikim" "ez ê bikim" "ez bikime" û dewamên xwe hemû demên xwe yek in lê mane dîsa ne yek in.
Eger min çê kir, şûna xwe li ku derê tînim? Ez difikirim li tewandina devokên kurdî yê ku "tabên" xwe hene "kurmancîya rojavayî", "kurmancîya standard" hsw. Lê tewandin ewqes cuda ye dixwaze ku wî şablona tewandina kurdî pir were guherîn.
Hûn çi dibêjin? (R: Hûn dewên çi?) [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 11:38, 7 tîrmeh 2026 (UTC)
:{{silav|Kurdanparez}} Binêre mînaka devoka ''rojhilatî'': [[Modul:ku-tewandin#L-135]] / [[Modul:ku-tewandin#L-138|L138]] / [[Modul:ku-tewandin#L-143|L-143]], .. Di nav modula ''ku-tewandin"-ê de tê çêkirin. [[Gotûbêja modulê:ku-tewandin#Paşgirên lêkeran di kurmanciya rojhilatî de]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 13:09, 7 tîrmeh 2026 (UTC)
::PS: Dema niha: [[Modul:ku-tewandin#L-128]]; dema borî: [[Modul:ku-tewandin#L-153]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 13:15, 7 tîrmeh 2026 (UTC)
:::Merheba @[[Bikarhêner:Ghybu|Ghybu]], Eger mimkin be tu dikarî yên devokî têxî binmodulekê û bila modul load bike, bi vî awayî belkî çêkirina yên nû û famkirina modulê hêsantir be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:09, 7 tîrmeh 2026 (UTC)
:::Lê di devokê reşwî de hem dema niha yê statîk heye (Îng: I do -> Ez dikim) û yê progressîf heye (I am doing -> Eîz (nêr) Ez î (mê) dikime, yan jî Ez î dikime/Ez ê dikime). Reşwî ji yê standardê pirtir bo zayend ferq tîne, hem jî li dema pêşî (Ez ê bikim (Nêr) ez a bikim (Mê)). Dema pêşî jî formên cuda hene (Ezêk bikime (I will be doing it) û ez ê/a bikim (I will do it))
:::Ev di modûlê têhel bê? [[Bikarhêner:Kurdanparez|Kurdanparez]] ([[Gotûbêja bikarhêner:Kurdanparez|gotûbêj]]) 22:09, 8 tîrmeh 2026 (UTC)
== WF:Xebat ==
Min sayfeya [[WF:Xebat]] çêkir, xeletiyên ku hûn ferq dikin ji kerema xwe lê zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:35, 15 tîrmeh 2026 (UTC)
== Modul:quote ==
@[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Keyomerd|Keyomerd]], @[[Bikarhêner:Ferhengvan|Ferhengvan]] Merheba, ez modula îngilîzî ya jêgirtin û çavkaniyan li gorî şablonên {{ş|jêder}} format dikim û ji bo ku hertişt wekî formata me ya niha be, gelek serê min diêşe. Ez dibêjim bila em dev ji formata niha berdin wekî ya îngilîzî bi kar bînin ji ber ku gelek xisûsiyetên îngilîzî hene ku yekî dikare biemilîne. Hûn ê bibêjin niha ji kû hat bîra tê, min got [[Wîkîferheng:Xebat#MediaWiki:Gadget-start.js|start.js]] nû bikim û min dît otomatîk çavkanî bi riya [[mw:Citoid]]ê bi riya DOI, ISBN û lînk tê çêkirin, min got wiya jî li skrîptê îlawe bikim loma min [[Wîkîferheng:Xebat#Mediawiki:Gadget-citoid-helper.js|Mediawiki:Gadget-citoid-helper.js]] hazir kir lê hin parametreyên Citoidê kêm bûn, min got ka wana rast bikim, min dît formata me ya niha ti standard bi kar nayîne tenê parametreyan bi virgulê li hev dixe.
Ez dibêjim formata îngilîzî baştir e, hem ne îtalîk e ku bi hêsanî tên xwendin, hem jî tarîx û çavkanî herî pêşî tên nîşandan ku ji bo peyitandinê bo min muhîm e. Modula îngilîzî de hema bêje her tişt hatiye çêkirin, eger em rûnin tenê yên lazim li şablona çavkaniyên xwe binivîsin ew ê gelek wext bistîne. Eger em modula îngilîzî bi kar bînin, em ê hem karibin şablonên îngilîzî bi hêsanî û rast kopî bikin hem jî kurdîkirina parametreyan gelek hêsan e. Fikir? --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:20, 24 tîrmeh 2026 (UTC)
: Ji bo miqayesekirinê bnr. [[:en:Template:quote-book]] / [[Şablon:jêder-kitêb/ceribandin]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:32, 24 tîrmeh 2026 (UTC)
::Bi min bila yê mewcûd jî bimîne, ew jî şiklen xweş û baştir e. Feqet bi min jî yên îngilîzî rehettir in, eger rastkirina yê mewcûd zehmet be, em rasterast wan ji xwe re bînin û tercime bikin. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 09:41, 24 tîrmeh 2026 (UTC)
:::Ez êvarê ve dixebitim, min nêzîk kiribû lê min got belkî hûn qane bibin lê xuya ye hûn ê hîn serê min gelek biêşînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:59, 24 tîrmeh 2026 (UTC)
::::@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Eger îtîrazek tinebe, ez ê îtalîk ji jêgirtinê rakim... Jixwe bi riya [jêgirtin] dikare were veşartin û her daim bi * dest pê dike loma bi hêsanî tê fêmkirin û xwendin rihettir dibe. Bnr. [https://ku.wiktionary.org/w/index.php?title=W%C3%AEk%C3%AEferheng:ceribandin/nim%C3%BBne&oldid=6277132#L%C3%AAker_2 ceribandinê] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:13, 24 tîrmeh 2026 (UTC)
== Request for comment (the future of Abstract Wikipedia) ==
<bdi lang="en" dir="ltr" class="mw-content-ltr">
Apologies if this has not yet been translated into your wiki's language. {{int:Please-translate}}
You are invited to voice your opinions in a [[:m:Requests for comment/The future of Abstract Wikipedia|request for comment about the future of Abstract Wikipedia]]. {{Int:Feedback-thanks-title}}
[[:m:User:Kowal2701|Kowal2701]] ([[:m:User talk:Kowal2701|talk]]) 12:25, 24 tîrmeh 2026 (UTC)
</bdi>
<!-- Message sent by User:DreamRimmer@metawiki using the list at https://meta.wikimedia.org/w/index.php?title=Distribution_list/Global_message_delivery&oldid=30513860 -->
== ''mê'' yan ''nêr'' li gorî maneyê / ''mê'' yan ''nêr'' (bikaranîn dudil e) ==
[//ku.wiktionary.org/w/index.php?title=xelat&diff=prev&oldid=6291766]: Mixabin pirr bikarhêner şûnda parametreya <code>z=m/n</code> ('' m yan n li gorî maneyê'') <code>z=mn</code> (''m yan n (bikaranîn dudil e)'') bi kar tînin ([[Şablon:navdêr#Parametre]]) ji ber ku di nav her du nivîsê de pir ferq nebînin. Nizanim em ê dîsa parametreya <code>z=nt</code> (''nêtar'') çêkin, wekî berê: [//ku.wiktionary.org/w/index.php?title=W%C3%AEk%C3%AEferheng:D%C3%AEwan/ar%C5%9F%C3%AEv_2019&oldid=6104681#Zayend]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:17, 28 tîrmeh 2026 (UTC)
:Merheba {{r|Ghybu}} {{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|kelîme|c=mn}}</nowiki>}}
Ya din jî; {{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|kelîme|c=m/n}}</nowiki>}}
Tu dixwazî em wana berevajî bikin? Not: nêtar ji bo kurmancî nabe, mn û m/n baş in. Berê ji ber ku m/n tinebû, her kes li gorî parametreyên heyî dixebitî. Niha hene bi min problem nîne eger em berevajî nekin.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 05:32, 28 tîrmeh 2026 (UTC)
: Bi min ev format baştir e:
{{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|kelîme|c1=m|c1etîk=Mêrdîn|c2=n|c2etîk=Dihok}}</nowiki>}}
Yan jî:
{{demo|sep=wisa xuya dibe:<br>|<nowiki>{{navdêr|ku|zayend|c1=n|c2=m|c1ref={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 1 Hejmar 1-23 (1932-1933)/1998|hejmar=4|sal=1938}}|c2ref={{Zana Farqînî}}}}</nowiki>}}
{{çavkanî}}
:--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 05:49, 28 tîrmeh 2026 (UTC)
::Feqat em dikarin li şûna {{param2|c|m/n}}, {{param2|c|mn?}} yan {{param2|c|mn!}} çêkin ji bo ku neyên tevlihevkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 06:00, 28 tîrmeh 2026 (UTC)
:::Ez jî dizanim ku "nêtar ji bo kurmancî nabe" ([//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj%3Afikirmend&diff=3063789&oldid=3063526]) jixwe min "nêtar" kiribû "[//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj:fikirmend&diff=prev&oldid=3063788 mê an nêr li gorî bikaranînê]". Feqet nizanim çima rind nayê fêm kirin: Ferhengvan jî ew parametreyê bi awayekî rast bi kar nayîne [//ku.wiktionary.org/w/index.php?title=Got%C3%BBb%C3%AAj%3Areh&diff=6235991&oldid=6235985]. {{ping|Bikarhêner}} ? Min heta niha pirr gotar serrerastkir... Em ê hertim kontrol bikinê: pêşta problemeke vitira tine bû :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:13, 28 tîrmeh 2026 (UTC)
::::Bi rastî kî li vira parametreyên ku te li jor nivîsandine bi kar tîne {{=D}} ? Ji bîr meke em li ser wîkîferhenga kurdî ne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:39, 28 tîrmeh 2026 (UTC)
:::::Ya muhîm hebûna sîstemekê ye, heta niha ti sîstemeke baş nehatiye çêkirin û ti rê nehatiye dayîn ji bikarhêneran re ku hîn bibin, hêdî hêdî em ê çi ji destê me were, çêkin û hîn bikin. Bi min atolyeyek lazim e em hin tiştan ji bikarhênerên din re hîn bikin. Mîsal min <code><nowiki>{{subst:beralîbike}}</nowiki></code> li wîkîpediyayê çêkir û ji bikarhênerekî re nîşan da çawa tê bikaranîn, wî dest bi bikaranînê kir. Ji ber ku bikarhênerekî mobîlê ye, nikaribû bi tenê serê xwe beralîkin çêke her daîm alîkarî ji kesên din dixwast ku pir carî ti kes nedihat. Însanên me ji aliyê teknîkî ve baş nebin jî, wextê bi riyeke baş îzah dikî, fam dikim şikir ji xwedê re.
:::::Welhasil, [https://ku.wiktionary.org/w/index.php?title=reh&diff=prev&oldid=6291839 reh] çêbû? Eger tu nabêjî nêtar be, min daxwaza te fam nekir. Tu çi dixwazî em çawa bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 28 tîrmeh 2026 (UTC)
::::::Nivîs dîsa "''mê yan nêr li gorî maneyê''" be (kategorî jî) feqet di nav parametreyê de <code>mn</code> neyê nivîsandin. Parametre wekî {{param2|z/c|nt}} bisekiniyana (an jî navekî din "g" an "c") feqet nivîs û kategorî bihata guhertin... Herwiha profîlên bikarhênerên ku tên vira wekî en.wiki nînin: li vir zêde namînin û tiştên ku hu dikin jî ji bîr dikin. PS: Di zimanê peştûyî de jî (nêzîkî zimanê kurmancî ye) peyvên "mê an nêr" (''common gender'') hene [[:en:Category:Pashto nouns with multiple genders]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:28, 28 tîrmeh 2026 (UTC)
:::::::"g", "c", "nt" gî behsa cureyekî xisûsî dikin loma nabin, feqat di nav [https://ku.wiktionary.org/w/index.php?title=Modul:r%C3%AAziman&oldid=6115355#L-306 Modul:rêziman] de koda me "mn" otomatîk dike "mnmane" min ji bo back-compatiblity wisa kiribû lê ya rast "mnmane" ya, em "mn" ji bo kurmancî rakin, hemû "mn" bikin "mnmane" û "m/n" jî rakin û bikin "mndudil", nabe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:44, 28 tîrmeh 2026 (UTC)
::::::::OK, bi min baş e:) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 28 tîrmeh 2026 (UTC)
:::::::::Îmkan heye şablona tewandinê jî wisa bikî ku hem di qismê tewandinê de hem jî aliyê rastê were bikaranîn? Wekî me [[Wîkîferheng:Dîwan/2026#Beşa_Tewandin_bo_kurmancî|li vir]] got? Wextê gelek dirêj be yan mndudil be, em dikarin bixin qismê tewandinê bi şiklê "ya mêr" û "ya nêr". Wekî din yên biçûk û standard bila li aliyê rastê bimînin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:54, 28 tîrmeh 2026 (UTC)
::::::::::Erê bi parametreya {{param2|hêl|çep}} tê çêkirin. Belkî bi JSê tiştekî vitra jî tê çêkirin: dema ku tablo pirr fireh ye (>450px) tablo wekî "veşartî" xuya bike...
::::::::::"''Wekî din yên biçûk û standard bila li aliyê rastê bimînin?''": '''Aliyê rast:''' Dema ku tu mînakan dixwînî bi hêsanî tabloya tewandinê jî seh dikî (pedagojîk e). '''Bin beşa Tewandinê:''' 1) Ji bo mobilan rind e, tablo ne ser maneya peyvê ye. 3) Şûnda maneyan direj dibe 3) Wêne baştir xuya dikin ji ber ku ne bin tabloyê ne. '''Encam''': yek pratîk e, yek jî pedagojîk e [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:27, 28 tîrmeh 2026 (UTC)
:::::::::::Di bin beşa Tewandinê de baştir xuya dike... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:28, 28 tîrmeh 2026 (UTC)
::::::::::::Erê {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:29, 28 tîrmeh 2026 (UTC)
:::::::::::::Feqat lazim e yên mezin wekî [[mustensîx]] di nav qutîyê de bê wekî [[:en:heval]] di mobîlê de ya me sayfeyê fireh dike, ya îngilîzî de dikarî qutîyê bişemitînî [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:44, 28 tîrmeh 2026 (UTC)
::::::::::::::Formata herî xweş [https://ku.wiktionary.org/wiki/mustens%C3%AEx#Kurmanc%C3%AE] resm jî lê tê tablo jî di cihê xwe de ye. Wekî din wextê tablo li aliyê rastê ye însan naxwaze resmekî jî lê zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 28 tîrmeh 2026 (UTC)
:::::::::::::::''overflow'' bi tagên "div"-an rind tên çêkirin. Em ê tabloyên ''NavFrame'' bi kar bînin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 28 tîrmeh 2026 (UTC)
::::::::::::::::Eger class inflection-table bi kar bînî, bi [[MediaWiki:Gadget-defaultVisibilityToggles.js#L-43]] ê ku dixwaze dikare gîya veşêre an nîşan bide. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:11, 28 tîrmeh 2026 (UTC)
:::::::::::::::::Min fêm nekir, biryara we çi ye? Hemû bin beşa Tewandinê? an jî herdu? Li gor wê şablon tê guherandin. Bi min ferq nake, her du jî baş in... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:19, 28 tîrmeh 2026 (UTC)
::::::::::::::::::Me [[#Beşa Tewandin bo kurmancî|li jor]] gotibû bila hemû di beşa Tewandinê de bin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::Erê, min xwand Bikarnêner tiştekî din gotibû... Tu bi botê çêdikî? Bi botê hemû tabloyan bê parmetreya "hêl" bike bin beşa "Tewandin"-ê. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:30, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::Va rabim botekê hazir bikim. Bi qasî min jê fam kir, tam nabêje hemû nekin, nabêje hemû bikin jî. Lê em hemû bikin çunkî du kes got erê. Cihê Tewandinê piştî Bi alfabeyên din û Formên alternatîv be an jî beriya wan piştî maneyê (piştî wajeya klasîk) be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:38, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::Eger di yekcarê de nayê çêkirin bi parametreya "hêl" ve çêke. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:42, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::[https://linkcount.toolforge.org/?project=ku.wiktionary.org&page=%C5%9Eablon%3Aku-tew%C3%AEn-nav 27 hezar] car tê bikaranîn, Eger behsa wextê dikî, ne problem e, eger behsa "tenê yên fireh bixin tewandinê" wê carê yên mê û nêr bixim? Te cewaba pirsê cihê tewandinê jî neda? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:48, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::::Lojîk dibêje ku piştî bi "alfabeyên din", "formên alternatîv" û "Ji wêjeya klasîk" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:55, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::::Feqet tabloya tewandinê dûr dikeve... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:00, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::::::Bi min "Bi alfabeyên din" bixin piştî Bilêvkirinê çûnkî @[[Bikarhêner:Ferhengvan|Ferhengvan]] pir caran ji bo nîşandana dengî alfaba erebî bi kar tîne, h3 rengê teal tenê ji bo "Navdêr, Lêker hwd." bi cssê bi kar bînin [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.css&diff=prev&oldid=6241199 wekî wisa lê berevajî], Piştî "Ji wêjeyê/klasîk" em tewandinê deynîn, "Formên alternatîv" piştî Tewandinê be û îro nekin, eger dixwazî di ceribandinê de tabloya nû hazir bike, em hefteyek li hêviya hevalên din bisekinin, eger îtîraz nehat, em çêkin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:15, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::::::Beşa "Bikaranîn" jî heye... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:28, 28 tîrmeh 2026 (UTC)
:::::::::::::::::::::::::::Piştî Bikaranînê bnr. [[:en:Wiktionary:Entry_layout#Headings_after_the_definitions]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:37, 28 tîrmeh 2026 (UTC)
::::::::::::::::::::::::::::(h3) Bilêvkirin, Bi alfabeyên din, Sinifa rêzimanî,
::::::::::::::::::::::::::::(h4) Ji wêjeyê/klasîk (divê were xistin bin maneyê), Bikaranîn, Tewandin, Formên alternatîv, Hevmane, Nêzîk, Dijmane, Jê, Têkildar, Etîmolojî (eger du sinifa rêzimanî ji eynî kokê hebe h3) Dûnde, Binêre herwiha, Werger [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:40, 28 tîrmeh 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}} {{r|Ferhengvan|Bikarhêner|Keyomerd|Zerwanda|Kurdanparez}} Fikir? --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:42, 28 tîrmeh 2026 (UTC)
:"Bi alfabeyên din" beşeke pirr hewce nîne, niha beşa "mane" li ser rûpelê dûr dikeve. Bi min "Bîlêvkirin" > Mane > Ji wêjeya klasîk (wekî mînak e) > Bikaranîn > Tewandin > Bi alfabeyên din > Formên alternatîv > ... Divê beşên ku herî zêde tên xwastin werin "ronîkirin". [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 03:07, 29 tîrmeh 2026 (UTC)
::''[[bileks|Bîl'eks]],'' "Bi alfabeyên din" gelek muhîm e, ev ji bo kurmancî "formên alternatîv"-a wîkiya îngilîzî ye, başûrî tenê bi alfaba erebî dinivîsin û wextê kelîmeyek digerin çawa zanibin hatin kîjan kelîmeyê? Bi kirîlî ne lazim e lê bi erebî mecbûrî ye. Ji bo nettir bikin, lazim e em beşa "Bi alfabeyên din" baştir bikin. Mane hinek li jêr be ne muhîm e, wîkiya îngilîzî de jî gelek car lazim e herî jêr ji bo dîtina maneyê. "Ronîkirin"/teal xelet tê famkirin, sankî "qismê sereke (h3) lazim e wê be û qismên din werin xistin binê wê" tê famkirin loma em tenê Mane ronî bikin bes e, ka [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6291927 vê cssê] biceribîne û dîsa fikrên xwe bibêje. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 07:20, 29 tîrmeh 2026 (UTC)
:1. '''Li ser cinsîyetê:''' Bi min, em malûmatên ekstra yên wek ''"li Dihokê nêr, li Serhedê mê"'' (Serhed:m|Dihok:n) li qismê ''"Bikaranîn/Emilandin"''ê bidin baştir e. Kelîme di ferhengan de wek form û cinsîyet bi halê standard cih digirin, eger li mintiqayek cuda be ew cudahî li qismê "Bikaranîn/Emilandin"ê wek malûmatek ekstra cih bigire çêtir e. Ferhenga ku em vê gavê ji bo standardkirinê esas digirin ''"Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî) - Tirkî ya Nû"'' de jî bi yek cinsîyetê cih dane kelîmeyan. Ji xeynî wê di ferhenga standardkirinê ya Weqfa Mezopotamyayê de jî ew kelîme bi yek cinsîyetê cih digirin. Tiştekî din jî bêjim, eger em şablonê wek "mndudil" bi nav bikin mimkin e ku rê bide şaşîyan. Çimkî gava mirov wê dibîne, mirov dibêje qey li ser cinsîyeta kelîmeyê dudilîyek heye, bi qenaeta min dudilîyek bi wî şiklî nîne. Divê her kelîme xwedî cinsîyetekê bin û îstîsnayên mintiqayî -ger were xwestin- ekstra were nîşandan. Lewma bi min halê mewcûd baş e. 2. '''Li ser şablonên tewandinê:''' Ger şablonên tewandinê di xuyabûna mobîl de bibin sedema xirabîyê, em wan şablonan ji bo mobîlan têxin qismê "Tewandin"ê. 3. '''Li ser rêzkirina qisman:''' Bi min divê mane li pêş be û ne hewce ye ku qismê "Bi alfabeyên din" derxin pêş. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 13:20, 29 tîrmeh 2026 (UTC)
::Li ser cinsiyetê erê, eger di ferhenga rastnivîsînê de cinsiyet hatibe nivîsandin ne hewce ye em "mndudil" binivîsin li qismê Bikaranînê behskirin bes e, lê eger tê de ev kelîme tinebe û di ferhengên din de jî dudiliyek li ser bikaranîna cinsiyetê hebe, wê demê "mndudil" lazim e bikaranîn çunkî wîkîferheng lazim e li gorî çavkaniyan were nivîsin. Li ser tewandinê ne mimkûnê em di mobîlê de di qismê Tewandinê de, di sermaseyê de em li qismê maneyê bixin loma lazim e em bi yek şiklî li qismê Tewandinê hemû bixin çêtir e. Li ser "Bi alfabeyên din" ji bo min zêde muhîm nîne, feqat dîsa jî ez dibêjim lazim e were pêşxistin. Tiştekî wisa jî heye eger di sayfeyekê de ji yekî zêdetir sinifên rêzimanî hebe wê demê lazim e Bi alfabeyên din bi sê = were bikaranîn, û cihê wê ne li dawiyê lê divê piştî Bilêvkirinê be.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:45, 29 tîrmeh 2026 (UTC)
:Bi a min beşa çevandinê li serê rûpelê be beşa maneyê pir dûrî çavan dikeve. Li jêrtir be çêtir e. [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 06:01, 31 tîrmeh 2026 (UTC)
::{{r|Wikihez}} Bi min di nav tabloyê de parametreya yekem jî were rakirin, ne hewce ye bi <nowiki>{{PAGNAME}}</nowiki> tê çêkirin. Di rûpelên din de jî (ne yên sereke) bi parametreya {{param2|nav}} were nivîsandin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 11:07, 31 tîrmeh 2026 (UTC)
:::Erê, maqûl e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:23, 31 tîrmeh 2026 (UTC)
::::cinsiyet jî m, n, mn be bes e ma bila wekî {{ş|navdêr}} be. cuda nebin [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:41, 31 tîrmeh 2026 (UTC)
:::::@[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Keyomerd|Keyomerd]], @[[Bikarhêner:Ghybu|Ghybu]] "Bikaranîn" an "Notên bikaranînê" ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:47, 31 tîrmeh 2026 (UTC)
:::::Wekî {{r|Bikarhêner}}î gotî zayenda peyvnan gorî devokan jî diguhere, divê ew jî bêne dîyarkirin. Herwiha beşa lêvandinê a ji bo pîtên cêwî pir tê paşguhkirin. Divê jê re cîyekî cihê bê hilanîn. Û ew jî wek zayendê bi devokan diguhere. Meselen tya pêşîn a "tişt" ê li ba me nerm e lê li deverên din hişk e. Û ji devla reqê (wek t-ya req) hişk bi kar bê çêtir e, jixwe her kes wê bi kar tîne nizanim req ji ku derketîye. [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 14:05, 31 tîrmeh 2026 (UTC)
:
<syntaxhighlight lang="json">
"subsections": [ "Ji wêjeya klasîk", "Ji wêjeyê", "Bikaranîn", "Tewandin", "Bi alfabeyên din", "Formên alternatîv", "Kurtkirin", "Sembol", "Navê zanistî", "Hevmane", "Dijmane", "Jê", "Nêzîk", "Jornav", "Jêrnav", "Têkildar", "Etîmolojî", "Dûnde", "Binêre herwiha", "Bi zaravayên din", "Werger", "Qafiye", "Anagram" ]
</syntaxhighlight>
{{r|Keyomerd|Ghybu|Bikarhêner}}Li gorî nîqaşa jor û [[Alîkarî:Formata nivîsînê]] min ev lîste hazir kir lê tê de hin kêmasî hene, "Bide ber" tê de tine ye, li kû derê bixim? Mihtemelen beşên din jî hene ku haya min ji wan nîne. Pirsek din, beşên nestandard wekî [https://ku.wiktionary.org/w/index.php?title=berf&diff=prev&oldid=6292461] çi bikim? Min got li dawiyê bixim berî Wergerê lê sankî piştî Wergerê be çêtir e?--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:24, 31 tîrmeh 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]], @[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Keyomerd|Keyomerd]] Eger îtîraz an fikrekî din neyê, ez ê beşên nestandard bixim beriya "Bi zaravayên din" eger ev beş tinebe, beriya "Werger". Ez ê "Nêzîk" jî bixim piştî Hevmane û li ser [[Alîkarî:Formata nivîsînê]] "rast" bikim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 1 tebax 2026 (UTC)
::Ez ê "Bikaranîn" jî bikim "Notên bikaranînê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:03, 1 tebax 2026 (UTC)
:::Jixwe bot hema bêje hazir e, eger dûre kesek rabe fikrekî din li pêş bixe, em dikarin biguherînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:04, 1 tebax 2026 (UTC)
::::Ev hinek zêdetir problematîk e feqat ez ê "Ji wêjeyê/a klasîk" jî bikim "Ji edebiyatê"/"Ji edebiyata klasîk". Tabî eger fikrekî din hebe, ne problem e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:05, 1 tebax 2026 (UTC)
:::::Na, bi min "wêje" bimînî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 1 tebax 2026 (UTC)
::::::Ok {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:23, 1 tebax 2026 (UTC)
:::::::"Bide ber" pêşiya "Binêre herwiha"
:::::::"Jê" pêşiya "Têkildar" piştî jornav, jêrnav [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:41, 1 tebax 2026 (UTC)
:::::Ji bo min "'''edebîyat'''" çêtir e, bila ne wêje be. "wêje" kelîmeyeke şaşçêkirî ye. Mahîyeta qisman (kîjan qism bi kêrî çi tê) ji navên wan nayê famkirin. "Binêre herwiha" û "Herwiha" tevlihev dibû, baş e we rast kirîye. Feqet mahîyeta wê qismê ("Binêre herwiha") hê jî ji navê wî nayê famkirin. Jixwe ger em bixwazin bi wî şeklî binivîsin jî divê em bikin "Herwiha binêre", ew jî zid hatîye nivîsandin. Ji xeynî wê, "Navê zanistî" na, "'''Navê îlmî'''" çêtir e. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 22:51, 2 tebax 2026 (UTC)
::::::Bi min beşa Navê zanistî werin rakirin, wekî en.wikt em li dawiyê yan li pêşiya maneya bi şablona {{ş|q}} binivîsin yan jî wê şablona îngilîzî çêkin ku enwikt bi kar tîne.
::::::Li wikipediyayê min çend car dîtiye Penaber49 jî "Binêre herwiha" dike "Binêre" loma guhartina wê wekî "Herwiha binêre" fikrekî maqûl e.
::::::Herwiha tu ji bo Bêhtir xwendin çi dibêjî [[Special:PermanentLink/6323554#Binêrê_herwiha]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:29, 3 tebax 2026 (UTC)
:::::::Ez dibêjim bila '''''"Xwendina Zêdetir"''''' be. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 05:03, 6 tebax 2026 (UTC)
:::Em çima peyva notê bi kar tînin, ma "nîşeyên bikaranînê" ne çêtir e? [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 06:04, 1 tebax 2026 (UTC)
:Rêzkirina dawî:
<syntaxhighlight lang="json">
"subsections": ["Ji wêjeya klasîk", "Ji wêjeyê", "Tewandin", "Notên bikaranînê", "Bi alfabeyên din", "Formên alternatîv", "Kurtkirin", "Sembol", "Navê zanistî", "Hevmane", "Nêzîk", "Dijmane", "Jornav", "Jêrnav", "Jê", "Têkildar", "Etîmolojî", "Dûnde", "Bide ber", "Binêre herwiha", "Bi zaravayên din", "Werger", "Qafiye", "Anagram"]
</syntaxhighlight>
Bnr. [[Wîkîferheng:Bot/Balyozbot/tercîh.json]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:44, 1 tebax 2026 (UTC)
:Bi botê ez ê tenê binebeşên sinifên rêzimanî yên bi 4 = biguherînim, yên din lazim e bi destî werin guhartin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 01:46, 1 tebax 2026 (UTC)
::@[[Bikarhêner:Ghybu|Ghybu]] Bota rêzkirina binebeşan û cihguhartina Tewandinê hema bêje hazir e. Param {{param|hêl}} bi kar bînim (eger wisa be, em kategoriya "ku-tewîn-nav bê param hêl" çêkin daku hemû xilas bikin) yan jî şablonên heyî bikim ku-tewîn-nav/kevn û piştî kontrolkirinê hemû bikin nû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:26, 1 tebax 2026 (UTC)
:::Bi parametreyê {{param2|hêl}} tu ê paşê wan dîsa rakî... Herwiha min va [https://ku.wiktionary.org/w/index.php?title=abes%C3%AEv&curid=43641&diff=6294632&oldid=6244736] fêm nekir, me çima Tabloya tewandinê rakir :)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:26, 1 tebax 2026 (UTC)
::::Ji bo ku rê bidin tiştên wisa :D Em ê li kû derê deynîn examples?? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 1 tebax 2026 (UTC)
:::::Tablo hazir bin, em dest pê bikin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 1 tebax 2026 (UTC)
::::::Sernav tê rakirin, mê bikim "m"? yan bila bimînî? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:28, 1 tebax 2026 (UTC)
:::::::Tablo hatiye çêkirin bi [https://ku.wiktionary.org/wiki/Modul:ku-tew%C3%AEn-nav/ceribandin vê] kodê her du parametre jî tên bikaranîn mê/m, nêr/n û mê û nêr/mn... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:41, 1 tebax 2026 (UTC)
:::::Ez nizanim çi bibêjim... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:43, 1 tebax 2026 (UTC)
::::::[https://ku.wiktionary.org/w/index.php?title=abes%C3%AEv&diff=prev&oldid=6294657] ok?
::::::Bi min li şûna {{param|hêl}} em {{ş|ku-tewîn-nav/nû}} bi [[Modul:ku-tewîn-nav/ceribandin|modula ceribandinê]] çêkin, paşê navê şablonê biguherînin baştir be, çunkî [[Special:PermanentLink/6294527#L-337|modula nû]] ne backward compatible e. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:56, 1 tebax 2026 (UTC)
:::::::Em ê dîsa paşê nivîsa "/nû" rakin. Bi botê 22000 rûpel di 2 rojan de tê guherandin? Eger wisa ye (bi min pirr nîne), bi navê rast biguhrîne, bila di nav rûpelên ku nehatine guherandin xeletî hebin... Dîsa jî tu dizanî, bot te çêkiriye û tu sererast dikî :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:17, 1 tebax 2026 (UTC)
::::::::Min gelek caran dîtiye, bi botê tiştekî hatiye guhartin lê ji ber ku rind nehatiye kontrolkirin, şablona kevn bi halê xwe yê kevn maye. Tirsa min ew e. Ji ber ku formata hemû sayfeyan ne eynî ne, ez dibêm bot gelek ji wan derbas bike. Bi vî awayî em ê {{ş|ku-tewîn-nav}} sêwî bihêlin û piştî bot xilas kir, em ê bi dil rehetî karibin şablona nû rakin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:26, 1 tebax 2026 (UTC)
:::::::::Rast e! {{param|g|2}} :)) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:32, 1 tebax 2026 (UTC)
::::::::::Mîsalên wan gelek in :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:36, 1 tebax 2026 (UTC)
:::::::::::Eger karibî hinek round edges bixî û rengê background a "Tewandina X" biguherînî, gelek moderntir û xweştir xuya bike :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:25, 1 tebax 2026 (UTC)
::::::::::::''Simplicity is best'' / ''Fewer choices mean less worry:'' felsefeya min e:) Me tabloyê wekî en.wîkî kir [https://ku.wiktionary.org/wiki/%C5%9Eablon:tabloya-tewandin%C3%AA-ser {tabloya-tewandinê-ser}], tenê [//ku.wiktionary.org/wiki/Modul:ku-tew%C3%AEn-nav/ceribandin#L-362 reng] ({{param2|palette}}) tê guherandin (felsefeya wan e jî...). Bi min tiştekî pir rind çêkirine. Dîsa jî em dikarin (wekî berî) bi CSSê biguherînin. Ji bo çêkirina tabloyan min di rûpela şablonê de tiştên ku pirr hewce ne nivisandiye ji bo detayan jî rûpela en.wiki heye. Herwiha tabloyê li gor daxwaza we biguherînin û serrast bikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 03:04, 2 tebax 2026 (UTC)
:::::::::::::PS: Şiklê tabloyê li vira tê guherandin [[Şablon:tewandin-ser/style.css]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 04:41, 2 tebax 2026 (UTC)
::::::::::::::Mala te ava tu ji heqê rengan gelek xweş derdikevî :)
::::::::::::::Ev felsefeya te gelek caran karê min pir zor dike {{=D}} en.witk gelek tiştî bi awayekî xweş çêkirine, ez jî heta ku wana adapte bikim, gelek êş dikşînim bnr. [[#Modul:quote]] û Şablonên etîmolojiyê [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:47, 2 tebax 2026 (UTC)
:::::::::::::::Bot xilas kiriye, Baş e [[Special:PermanentLink/6323268#Şablona_ku-tewîn-nav_ne_li_binê_Serenav_an_Navdêrê_ye|zêde]] xeletî derneketin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 2 tebax 2026 (UTC)
::::::::::::::::Gelek spas! [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:42, 2 tebax 2026 (UTC)
:::::::::::::::::Niha şablon sêwî ma ji xeynî gotûbêjan, ew jî dûrê rast bikim, tu dikarî modulan rojane bikî? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:28, 3 tebax 2026 (UTC)
::::::::::::::::::Spas. Ev rûpel pirr dirêj bû... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:33, 3 tebax 2026 (UTC)
:::::::::::::::::::Erê, rêzkirina beşan ez ê bikim yek satir, lê dîsa jî zêde fêde nede, fikrekî din heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:38, 3 tebax 2026 (UTC)
::::::::::::::::::::@[[Bikarhêner:Ghybu|Ghybu]] [https://ku.wiktionary.org/wiki/W%C3%AEk%C3%AEferheng:D%C3%AEwan#c-Wikihez-20260801005000-Wikihez-20260731162400] û [https://ku.wiktionary.org/w/index.php?title=gur&curid=5384&diff=6328092&oldid=6328089] ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:00, 7 tebax 2026 (UTC)
== SEO ==
Merheba @[[Bikarhêner:Ghybu|Ghybu]] Wextê ''jiyannas'' bi şiklê "jiyannas" lê digerî di nav netîceyên googlê de [[jiyannas]] derdikeve? Min hem bi Chromeê û hem jî bi VPNê ceriband lê [[jiyannaseke]] derdikeve feqat [[jiyannas]] dernakeve. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:31, 2 tebax 2026 (UTC)
:"zengilok" derdikeve [[zengilok]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:38, 2 tebax 2026 (UTC)
:Min jî tenê [https://usercontent.one/wp/portal.arsivakurd.org/wp-content/uploads/2024/08/Bernamegeh-4.pdf?media=1709107483 va] (''Berna Megeh'', Jan Dost, OCCO MAHABAD) û https://portal.arsivakurd.org/wp-content/uploads/2022/06/Xwebun-34.pdf va] (''Xwebûn'', hj.34) dît herwiha ''Jihannasî'' jî heye... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:44, 2 tebax 2026 (UTC)
::Behsa çavkaniyan nakim behsa SEOyê dikim, çima [[jiyannas]] di netîceyan de dernakeve. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:49, 2 tebax 2026 (UTC)
:::Min fêm nekir, pirsa te çima Wîkîferheng dernakeve ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:51, 2 tebax 2026 (UTC)
::::Erê çima sayfeya Wîkîferhengê ya [[jiyannas]] dernakeve feqat [[jiyannaseke]] derdikeve. Min du nimûne nivîsandin ka piştî mehekê em binêrin derkeve yan na. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:54, 2 tebax 2026 (UTC)
:::::Bi min dema ku motora googlê "jiyannaseke" (-eke bi kurdî ye) digere, ew peyv tenê di kurdî de heye ji ber vê yekî dide. Feqet "Jiyannas" di nav zimanên din de jî heye (belkî difikire ku ew xeletî ye.). Herwiha nivîsa bin "Jiyannas" li gor "jiyannaseke" biçûk e û li bin "biolog" hatiye nivîsandin:) Herwiya peyva "biolog" jî dide ew jî giridayî pirr peyvan e... Pirr parametre hene... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:09, 2 tebax 2026 (UTC)
::::::Wextê li "birakujî" dictionnaire digerî netîceya ewil rasterast fr.wikt e. Wana tiştekî kirine lê çi? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:13, 2 tebax 2026 (UTC)
:::::::"Trust Score" ? Herwiha peyva "birakujî" pêş Wîkîfehengê hatiye çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:21, 2 tebax 2026 (UTC)
::::::::Nizanim [[:en:Wiktionary:Votes/2023-08/Installing the WikiSEO extension]] heye lê [[:phab:T346590]] sekinî ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:27, 2 tebax 2026 (UTC)
:::::::::Erê, feqet lînkên û navên Wîkîferhenga fransî û îngilîzî di internetê de pirr tên parvê kirin. Wîkîferhenga fansî ferhengek derxistiye: [https://www.fnac.com/a13530183/Collectif-Le-dico-adapte-du-Wiktionnaire Le dico] (li ser Amazonê). Kî qala Wîkîferhenga kurdî dike? Ji ber vê yêke hertim pêş me ne. Daxwaza wan werin pêş malperên din... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:36, 2 tebax 2026 (UTC)
::::::::::[https://www.google.com/search?sca_esv=a854c327b7bb0786&q=%22W%C3%AEk%C3%AEferheng%22&tbm=nws&source=lnms&fbs=ABfTbFUDadgeu2mn4mYJ8iEZ1GUDDuwRa4um-2LNhBFWIpqZc7ZL_t3iAe0lB1D4hb_Ohp7yg8FO-XwZ7R-ba4YefvIQCF4Ra4wbUNQStQ8dVc6IK2vWHoXbgHLwJobq4HTNMksBhYHBmxw2hK9Sa4pHAI3I2Ovzox54EiEId7q7IoMJRkRlr-XDezqDqx33xDsZIWuHy-a_vqoqDlYrhNBbNeVEP9MwaA&sa=X&ved=2ahUKEwiz94Co9IKWAxXNUaQEHQJAAGYQ0pQJegQIGBAB&biw=1320&bih=583&dpr=1 Wîkîferheng] {{=D}} Li ser X û medyayên din de jî pirr qala Wîkîferhengê nakin... Bi rastî kî Wîkîfrhengê dixwîne? [https://pageviews.wmcloud.org/topviews/?project=ku.wiktionary.org&platform=all-access&date=yesterday&excludes=]... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:41, 2 tebax 2026 (UTC)
:::::::::::Haya ti kesî ji Wîkîferhengê nîne bi rastî loma pêşxistina SEOyê hewce ye. Ez dibêjim miheqeq fr.wikt tiştek çêkiriye ne mimkun e wextê heval dictionnaire binivîsî wiktionary derkeve bêyî çêkirina tiştekî. Not: Min ferq kir li ser fr.wikt wextê bi mobîlê tê dikevî, eger di sayfeyekê de du ziman hebe ya fransî otomatîk vedibe zimanê din veşartî dimîne. Fikrekî xweş e em jî çêkin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 2 tebax 2026 (UTC)
::::::::::::Erê bila were çêkirin. Min gotûbêja wan li ser vê mijarê xwandibû, Wîkîferhenga fransî jî ne razî ye. Dema ku li ser Googlê tu peyvek diguherî Wîkîferhenga wan di bin Wîkîpediyayê û di bin malperên din de derdikeve. Û çend caran şûnda "Wiktionnaire" "Wikipédia" tê nivîsansin... Ew jî nizanin çi bikin :)) Niha dizanim ku en.wikt ji ne razî ye... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:37, 2 tebax 2026 (UTC)
:::::::::::::@[[Bikarhêner:Ghybu|Ghybu]], @[[Bikarhêner:Wikihez|Wikihez]]: Ez dibêjim rûpelên ku tenê form di wan de hene, divê <nowiki>__NOINDEX__</nowiki> bin. Wî çaxî, rêza wan nikare ji lemmayê bilindtir be ji ber ku wê nînbin. Ez dibêjim belkî îhtîmala derketina lemmayê bêhtir jî bibe heke netîceyên ku dişibin wê nînbin. Jixwe lemma wê derkeve heke yek li formekê bigere, ji ber ku di rûpelê lemmayê de tabloyên tewandinê hene û ev tên jimartin. [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 22:55, 2 tebax 2026 (UTC)
::::::::::::::@[[Bikarhêner:Guherto|Guherto]] Em dibêjin tenê form xuya dike ya sermade xuya nake tu dibêjî em formê noindex bikin {{=D}}
::::::::::::::Claude [[Special:PermanentLink/6323375|li vir]] dibêje ya ku Ghybu got rast e, ji ber ku frwikt gelek tê bikaranîn bêtir dikeve ber çavê google. Eger rojek Wikiseo li en.wikt were çêkirin, em jî bixwazin qet nebe kêrî tiştekî were.
::::::::::::::Em şablonek ji bo metadescription çêkin? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:09, 2 tebax 2026 (UTC)
:::::::::::::::@[[Bikarhêner:Wikihez|Wikihez]] Min got, ''Wî çaxî, rêza wan nikare ji lemmayê bilindtir be ji ber ku wê nînbin. Ez dibêjim belkî îhtîmala derketina lemmayê bêhtir jî bibe heke netîceyên ku dişibin wê nînbin.'' [[Bikarhêner:Guherto|Guherto]] ([[Gotûbêja bikarhêner:Guherto|gotûbêj]]) 23:10, 2 tebax 2026 (UTC)
:::::::::::Em jegirtinan lînk bikin? Bi vî sayî gelek sayfe lînk bide ser hev. Bi riya cssê em dikarin rengê tarî bikin da ku wekî lînk xuya nekin... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:39, 3 tebax 2026 (UTC)
::::::::::::Wextê jîwar ferheng yan jî boçik ferheng lê digerî rasterast Wîkîferheng dertê. Meselê hejmara bikaranîna eynî kelîmeyê di sayfeyê de ye. Em çiqas jêgirtin lê zêde bikin ewqas baştir dibe netîce :) Google AI Mode rasterast Wîkîferheng wek çavkanî nîşan û lînk dide loma kurmanc bi kar neyînin jî AI bi kar tînin baş e ... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:24, 3 tebax 2026 (UTC)
:::::::::::::Êêêê Google AI Overview çi bi şikle "tarîxiyen" ferheng lê digerim wekî ku min li ferhengê qeyd kiriye gelek xweş îzah dike (ji ber ku hîn gelek nû ye netîceyan de Wîkîferheng dernakeve) Nimûneyên bikaranînê jî ji înternetê derdixe. Çi mana ferhengê nemaye... Em çi îşî dikin li vir :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:47, 3 tebax 2026 (UTC)
:::::::::::::Êdî Google jî nayê bikaranîn :)) Niha kî tere ser ''Stack Overflow'' û di nav belgeyên kodan de digere... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:47, 3 tebax 2026 (UTC)
::::::::::::::Erê welle, ez gelek car dibînim kesên derdora min hemen Gemini an Chatgpt vedikin, çi tiştê biçûk hebin, hemen pirs dikin, qet li ser Googlê jî lê nagerin :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:52, 3 tebax 2026 (UTC)
:::::::::::::::"êgsig ferheng" netîce nade feqat wexta "ornag ferheng" digerim rasterast wîkîferheng derdikeve li binê dibêje "Tewandina ornag. Zayenda nêr a binavkirî. Rewş, Yekjimar, Pirjimar. Navkî, ornag, ornag. Îzafe, ornagê · ornagên. Çemandî, ornagî · ornagan.Read more"
:::::::::::::::Meraq dikim guhartina cihê tewandinê çi netîce bide. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:21, 3 tebax 2026 (UTC)
::::::::::::::::Pêşta me tabloya Tewandinê vitira dinivîsand: [[ketin (Tewandin)]] Dma ku nav hat guherandin li ser Googlê tewandin winda bû...
::::::::::::::::Min tewandin:kirin geriya tabloya tewandina Wîkîferhengê dide. Êdî kes nê vira:) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:38, 3 tebax 2026 (UTC)
:::::::::::::::::Tenê dema "niha" nivîsand. Min jî bi kurdî "dema bê jî binivîse" nivîsî: ew jî nivîsand... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:42, 3 tebax 2026 (UTC)
::::::::::::::::::[https://support.google.com/webmasters/answer/16908024?hl=en tê asteng kirin]... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 02:07, 4 tebax 2026 (UTC)
:::::::::::::::::::kî dibêje? li wir tiştek wisa nabêje...? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 02:16, 4 tebax 2026 (UTC)
::::::::::::::::::::Va hatiye nivîsandin: "''Exclude my site's links and content from Search generative AI features: Your site's content is prevented from being visible to users in Search generative AI features, including being linked to within these features and helping with grounding AI responses in these features. You won't receive any traffic or impressions from these features. Learn more about what excluding your site means.''" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 02:23, 4 tebax 2026 (UTC)
== Moda tarî ==
Bi chrome û Firefoxê wextê Reng dikî "otomatîk", eger tarî be, tiştên bi reng xirab xuya dikin, lê wextê "tarî" hildibijêrî xweş xuya dikin. Min hemû js/css/Gadget betal kirin û ?safemode=1 ceriband, dîsa jî wextê "otomatîk" e, her tiştê tarî xirab e. Êdî nizanim problem li cem min e yan di Moda tarî de ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:15, 9 tebax 2026 (UTC)
== Formata jêgirtinan ==
Merheba hevalno {{r|Ghybu|Bikarhêner|Keyomerd|Kurdanparez|Guherto|Şêr}} Wekî min [[#Modul:quote|li jor]] jî gotiye, formata îngilîzî ji ya me baştir e. Her tişt di cihên xwe de ne. Ya me wexta çend tercime yan jî transliterasyon îlawe dikî, gelek tevlihev dibe. Berê me çavkaniyê bi amûra [[MediaWiki:Gadget-jeder.js]] kurt kiribû feqat ew jî ne tiştekî baş e ji ber ku gelek malûmat tên veşartin û kesên nû tên tenê sal û nivîskarê dinivîsin carinan ew jî ne tiştekî baş e. Em dikarin amûra jêderê wisa eyar bikin ku bêyî parentez bixebite û ewilî vekirî be, dûre ê ku dixwaze malûmatan kurttir bike. Eger em vî tiştî bi formata me ya niha bikin, jêgirtinên bi çavkaniyên dirêj dê gelek xirab xuya bikin.
Hûn dikarin li vir miqayese bikin ka kîjan baştir xuya dike:
# [[Special:PermanentLink/6329434|jêgirtina dirêj bi îngilîzî bi formata en.wikt]] vs. [[Special:PermanentLink/6329414|bi formata me ya niha]]
# [[Special:PermanentLink/6329439|jêgirtineke bi soranî bi formata en.wikt]] vs. [[Special:PermanentLink/6329437|jêgirtineke bi soranî bi formata me ya niha]]
Ez naxwazim xwe wisa nîşan bidim ku li ser eynî tiştî her daîm israr dike feqat bi ya min ev zarûriyeteke ji bo baştirkirina xwendina nimûneyan e. Loma ez li vir dengdanek li dar dixim, bila qerar çi be, em wisa bikin, dûre em dikarin amûreke nû çêkin ku li gorî tercîhên bikarhênerî formata en.wikt yan jî formata niha were bikaranîn.
Ji kerema xwe raya xwe li jêr bidin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:01, 11 tebax 2026 (UTC)
=== Dengdan ===
{{dengdan|destpêk=11.08.2026|dawî=18.08.2026}} <!-- kengî dengdan xilas bû vê şablonê {{dengdan/borî|destpêk=|dawî=}} bi kar bînin. -->
==== Bila formata niha bimîne ====
<!-- Li jêr '# {{erê}} ~~~~' binivîse ji bo raya erê. -->
==== Bila formata en.wikt were bikaranîn ====
<!-- Li jêr '# {{erê}} ~~~~' binivîse ji bo raya erê. -->
# {{erê}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 11 tebax 2026 (UTC)
# {{erê}} [[Bikarhêner:Keyomerd|Keyomerd]] ([[Gotûbêja bikarhêner:Keyomerd|gotûbêj]]) 14:40, 16 tebax 2026 (UTC)
=== Gotûbêj ===
{{şirove}} Eger hûn bibêjin çima sal û çavkanî li herî pêş e sebeb ev e: Ji bo en.wiktê "attestion" yanî peyitandin bi riya emilandinên heqîqî pêk tê. Yanî eger di ferhengekê de ev kelîme derbas bibe ji bo wan ne muhîm e, ya muhîm emilandina di nav cumleyan de ye. Mîsal ew kelîmeyên ku tenê ferhengan de derbas dibin dixin [[:en:Appendix:English dictionary-only terms]]. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:14, 11 tebax 2026 (UTC)
:@[[Bikarhêner:Ferhengvan|Ferhengvan]], @[[Bikarhêner:Bibliophile|Bibliophile]] û @[[Bikarhêner:Dûrzan|Dûrzan]] fikrê we çi ye li ser vê meseleyê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:48, 12 tebax 2026 (UTC)
::Ez ji tiştên teknîk pirr fêhm nakim. Çewa baş be ên ku pispor in bila biryar bidin... [[Bikarhêner:Dûrzan|Dûrzan]] ([[Gotûbêja bikarhêner:Dûrzan|gotûbêj]]) 21:04, 12 tebax 2026 (UTC)
== Guhartina rêbaznameyê ==
Ez fam nakim me çima di rêbaznameyê de gotiye twitter nayê hesibandin wextê çavkanîdayînê. en.wikt bi kar tîne û eger wek cimleyek derbas bibe bi min ti problem nîne. Fikr? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 14 tebax 2026 (UTC)
:Heta twitter ji bikaranîna ferhengan hîn baştir e, qet nebe em emilandina kelîmeyekê ya eseh dibînin... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:07, 14 tebax 2026 (UTC)
1j5ouzloe7cclzmvrrzt2wv7sh55zio
Şablon:J:Hesen Huseyîn Denîz:Ro û Zê
10
1356618
6334530
6229958
2026-08-18T00:51:45Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334530
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Hesen Huseyîn Denîz}}|sernav=Ro û Zê|weşanxane={{w|Do}}|sal=2011|isbn=978-605-4427-25-3}}<!--
-->{{kategorî|Jêgirtinên ji Hesen Huseyîn Denîz}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesen Huseyîn Denîz]]
{{jgcat|ku}}
</noinclude>
8q2c964wm33dam61axeff27t12nsgtl
6334584
6334530
2026-08-18T11:15:58Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesen Huseyîn Denîz/Ro û Zê]] weke [[Şablon:J:Hesen Huseyîn Denîz:Ro û Zê]] guhart: Bot: Rûpel bar kir
6334530
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Hesen Huseyîn Denîz}}|sernav=Ro û Zê|weşanxane={{w|Do}}|sal=2011|isbn=978-605-4427-25-3}}<!--
-->{{kategorî|Jêgirtinên ji Hesen Huseyîn Denîz}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Hesen Huseyîn Denîz]]
{{jgcat|ku}}
</noinclude>
8q2c964wm33dam61axeff27t12nsgtl
Şablon:J:Tehsîn Îbrahîm Doskî:Şerha Dîwana Ehmedê Xanî
10
1356625
6334563
6230077
2026-08-18T01:03:23Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334563
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Tehsîn Îbrahîm Doskî}}|sernav=Şerha Dîwana Ehmedê Xanî|weşanxane={{w|Nûbihar}}|sal=2025|isbn=978-625-7383-67-7}}<!--
-->{{kategorî|Jêgirtinên ji Tehsîn Îbrahîm Doskî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Tehsîn Îbrahîm Doskî]]
{{jgcat|ku}}
</noinclude>
7v2geq5dso6mywjbfx1pv42k5fbl33u
6334672
6334563
2026-08-18T11:17:18Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Tehsîn Îbrahîm Doskî/Şerha Dîwana Ehmedê Xanî]] weke [[Şablon:J:Tehsîn Îbrahîm Doskî:Şerha Dîwana Ehmedê Xanî]] guhart: Bot: Rûpel bar kir
6334563
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Tehsîn Îbrahîm Doskî}}|sernav=Şerha Dîwana Ehmedê Xanî|weşanxane={{w|Nûbihar}}|sal=2025|isbn=978-625-7383-67-7}}<!--
-->{{kategorî|Jêgirtinên ji Tehsîn Îbrahîm Doskî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Tehsîn Îbrahîm Doskî]]
{{jgcat|ku}}
</noinclude>
7v2geq5dso6mywjbfx1pv42k5fbl33u
Şablon:J:Ehmedê Xanî:Dîwana Ehmedê Xanî
10
1356729
6334476
6230713
2026-08-17T23:51:54Z
Wikihez
7344
?
6334476
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Ehmedê Xanî}}|sernav={{w|Dîwana Helbestan|Dîwan}} (helbest)|weşanxane=|sal=sedsala XVIIan}}<!--
-->{{kategorî|Jêgirtinên ji Ehmedê Xanî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ehmedê Xanî]]
{{jgcat|ku}}
</noinclude>
koxq45pilwnc32bwnhlvt0v91kegx6v
6334478
6334476
2026-08-17T23:53:00Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ehmedê Xanî/Dîwana Ehmedê Xanî]] weke [[Şablon:J:Ehmedê Xanî:Dîwana Ehmedê Xanî]] guhart
6334476
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|Ehmedê Xanî}}|sernav={{w|Dîwana Helbestan|Dîwan}} (helbest)|weşanxane=|sal=sedsala XVIIan}}<!--
-->{{kategorî|Jêgirtinên ji Ehmedê Xanî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ehmedê Xanî]]
{{jgcat|ku}}
</noinclude>
koxq45pilwnc32bwnhlvt0v91kegx6v
Şablon:J:Ciwanmerd Kulek:Zemanekî li Sê Welatan
10
1356744
6334462
6230815
2026-08-17T23:37:18Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ciwanmerd Kulek/Zemanekî li Sê Welatan]] weke [[Şablon:J:Ciwanmerd Kulek:Zemanekî li Sê Welatan]] guhart
6230815
wikitext
text/x-wiki
{{kitêb|nivîskar={{w|Ciwanmerd Kulek}}|sernav=Zemanekî li Sê Welatan|weşanxane={{w|Pall}}|sal={{w|2025}}|rûpel={{{r|}}}|ziman=ku|isbn=978-625-98311-5-2}}<!--
-->{{kategorî|Jêgirtinên ji Ciwanmerd Kulek}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ciwanmerd Kulek]]
[[Kategorî: Çavkanî]]
</noinclude>
5tzfov0ob83nu1b06itsdcnbynfqu6b
6334464
6334462
2026-08-17T23:38:08Z
Wikihez
7344
6334464
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar={{w|Ciwanmerd Kulek}}|sernav=Zemanekî li Sê Welatan|weşanxane={{w|Pall}}|sal=2025|rûpel={{{r|}}}|isbn=978-625-98311-5-2}}<!--
-->{{kategorî|Jêgirtinên ji Ciwanmerd Kulek}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Ciwanmerd Kulek]]
{{jgcat|ku}}
</noinclude>
r3pvvuwghvxz9rti9pt795rh7vpzdb2
Şablon:J:M. Xalid Sadinî:Mela Huseynê Bateyî
10
1356812
6334540
6231335
2026-08-18T00:57:49Z
Balyozbot
7867
/* top */ sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334540
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|M. Xalid Sadinî}}|sernav=Mela Huseynê Bateyî|weşanxane={{w|Nûbihar}}|sal=2022|isbn=978-9994-360-66-1}}<!--
-->{{kategorî|Jêgirtinên ji M. Xalid Sadinî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|M. Xalid Sadinî]]
{{jgcat|ku}}
</noinclude>
tbzd5e6ley6c6dlfglkiarb3smomehz
6334604
6334540
2026-08-18T11:16:12Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/M. Xalid Sadinî/Mela Huseynê Bateyî]] weke [[Şablon:J:M. Xalid Sadinî:Mela Huseynê Bateyî]] guhart: Bot: Rûpel bar kir
6334540
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar={{w|M. Xalid Sadinî}}|sernav=Mela Huseynê Bateyî|weşanxane={{w|Nûbihar}}|sal=2022|isbn=978-9994-360-66-1}}<!--
-->{{kategorî|Jêgirtinên ji M. Xalid Sadinî}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|M. Xalid Sadinî]]
{{jgcat|ku}}
</noinclude>
tbzd5e6ley6c6dlfglkiarb3smomehz
Şablon:J:Têmûrê Xelîl:Nezerê Mêrxas
10
1356848
6334564
6231943
2026-08-18T01:05:30Z
Balyozbot
7867
sererastkirin (bi destî) [[Wîkîpediya:AutoWikiBrowser|AWB]]
6334564
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sal=1937|nivîskar=Têmûrê Xelîl Mûradov |sernav=Nezerê Mêrxas}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Têmûrê Xelîl Mûradov ]]
{{jgcat|ku}}
</noinclude>
j4guhvxq4fpd7is13sqshiti8awnoqa
6334674
6334564
2026-08-18T11:17:20Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Têmûrê Xelîl/Nezerê Mêrxas]] weke [[Şablon:J:Têmûrê Xelîl:Nezerê Mêrxas]] guhart: Bot: Rûpel bar kir
6334564
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|sal=1937|nivîskar=Têmûrê Xelîl Mûradov |sernav=Nezerê Mêrxas}}<noinclude>
{{belgekirin}}
[[Kategorî:Jêgirtin li gorî nivîskaran ({{navê zimanî|ku}})|Têmûrê Xelîl Mûradov ]]
{{jgcat|ku}}
</noinclude>
j4guhvxq4fpd7is13sqshiti8awnoqa
Şablon:ku-Latn/belge
10
1357052
6334359
6233561
2026-08-17T16:10:23Z
Wikihez
7344
6334359
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
== Bikaranîn ==
<syntaxhighlight lang="wikitext">{{ku-Latn|<peyv bi tîpên latînî>}}</syntaxhighlight>
== Nimûne ==
{{demo|sep=dide:<br>|<nowiki>{{rastnivîs|ku|nêrîn#Navdêr|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
== TemplateData ==
{{TemplateData header}}
<templatedata>
{
"params": {
"1": {
"label": "madeya ku were lînkkirin"
},
"w": {
"label": "maneya kelîmeyê"
}
},
"paramOrder": [
"1",
"w"
],
"description": "Ji bo beralîkirina madeya bi alfabeya latînî ya kurmancî ji madeya bi alfabeya erebî"
}
</templatedata>
== Binêre ==
* {{ş|ku-Arab}} (kevn)
* {{ş|ku-ar}}
* {{ş|ckb-Arab}}
* {{ş|sdh-Arab}}
* {{ş|hac-Arab}}
<includeonly>
[[Kategorî:Şablonên kurmancî]]
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
a46uzhw27yxm2o87bfbzepxj5ov35pe
6334360
6334359
2026-08-17T16:11:09Z
Wikihez
7344
6334360
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
== Bikaranîn ==
<syntaxhighlight lang="wikitext">{{ku-Latn|<peyv bi tîpên latînî>}}</syntaxhighlight>
== Nimûne ==
{{demo|sep=dide:<br>|<nowiki>{{ku-Latn|nêrîn|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
== TemplateData ==
{{TemplateData header}}
<templatedata>
{
"params": {
"1": {
"label": "madeya ku were lînkkirin"
},
"w": {
"label": "maneya kelîmeyê"
}
},
"paramOrder": [
"1",
"w"
],
"description": "Ji bo beralîkirina madeya bi alfabeya latînî ya kurmancî ji madeya bi alfabeya erebî"
}
</templatedata>
== Binêre ==
* {{ş|ku-Arab}} (kevn)
* {{ş|ku-ar}}
* {{ş|ckb-Arab}}
* {{ş|sdh-Arab}}
* {{ş|hac-Arab}}
<includeonly>
[[Kategorî:Şablonên kurmancî]]
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
bbf2l1dzxuvtcpwse2899f96k9ljyt6
6334362
6334360
2026-08-17T16:12:48Z
Wikihez
7344
/* Nimûne */
6334362
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
== Bikaranîn ==
<syntaxhighlight lang="wikitext">{{ku-Latn|<peyv bi tîpên latînî>}}</syntaxhighlight>
== Nimûne ==
Di rûpela {{g|ku|نێرین}} de:
{{demo|sep=dide:<br>|<nowiki>{{ku-Latn|nêrîn|w=[[ray]], [[dîtin]], [[bawerî]]}}</nowiki>}}
== TemplateData ==
{{TemplateData header}}
<templatedata>
{
"params": {
"1": {
"label": "madeya ku were lînkkirin"
},
"w": {
"label": "maneya kelîmeyê"
}
},
"paramOrder": [
"1",
"w"
],
"description": "Ji bo beralîkirina madeya bi alfabeya latînî ya kurmancî ji madeya bi alfabeya erebî"
}
</templatedata>
== Binêre ==
* {{ş|ku-Arab}} (kevn)
* {{ş|ku-ar}}
* {{ş|ckb-Arab}}
* {{ş|sdh-Arab}}
* {{ş|hac-Arab}}
<includeonly>
[[Kategorî:Şablonên kurmancî]]
[[Kategorî:Şablonên lînkkirina daxîlî]]
</includeonly>
1fg0w6wplt39midn5gr38qn6ivc38o2
tarîxçî
0
1357406
6334307
6329928
2026-08-17T12:45:22Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334307
wikitext
text/x-wiki
{{bnr-h|tarixçi}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
{{navdêr|ku|c=mn}}
# {{e|ku|meslek}} Kesê ku li ser [[tarîx]]ê dinivîse an lê dikole.<ref>{{Ç:Jaba:1879|تاریخچی<ts:tarikhtchi><sc:Arab>|r=92}}</ref>
#: {{hevmane|ku|tarîxnas|dîroknas|dîrokvan|dîrokzan|mêjûnas|mêjûzan}}
#* {{jêder-kovar|ku|jêgirtin='''Tarîxçîkî''' Îngîlîz navê wî Arnold Tombey e, dibêje: "Tirk bi tarîxê nizanin, ku li wan teng bibe karin Hezretê Nebî Silêman û Qanûnî Siltan Silêman bikin yek, tenê ji navê wan."|kovar=Welat (Rojnameya Hefteyî)|sernav=TC dewletî û dewletên Tirkan yên nû|nivîskar=w:Musa Anter|hejmar=62|sal=1993|rûpel=7}}
#* {{jêder-kovar|ku|jêgirtin=Malzemeyê dîrokzan (yan jî dîroknas – zimanzanên kurd(!) hêj biryar nedane ku ew ji ”'''tarîxçî'''”yan re bibêjin dîrokzan yan dîroknas.) belgeyên rastîn û şîroveyên îlmî, zanîstî ne.|kovar=Agirî|sernav=Edebiyat û Dîrok|nivîskar=w:Loqman Polat|hejmar=61|sal=2007|rûpel=11}}
#* {{jêder-malper|ku|jêgirtin='''Tarîxçî''' û teorîsyenên Tirk, Ziya Gökalpê Diyarbekirî hosteyê ilmê xwe yê sosyolojîyê dihesibînin.|nivîskar=w:Omer Faruk Baran|sernav=Zewaca Ziya Gökalp û sosyolojîyeka sînesoj|url=https://www.botantimes.com/zewaca-ziya-gokalp-u-sosyolojiyeka-sinesoj/|malper=Botan Times|sal=2023}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Etîmolojî ====
Deyn ji {{deyn|ota|ku|tr=tarihçi}}. {{anlz|ku|tarîx|-çî}}.
==== Werger ====
{{werger-ser|kesê ku li ser tarîxê dinivîse an lê dikole}}
* {{Z|sq}}: {{W+|sq|historian}}
* {{Z|de}}: {{W+|de|Historiker|n}}, {{W+|de|Historikerin|m}}
* {{Z|az}}: {{W+|az|tarixçi}}
* {{Z|eu}}: {{W-|eu|historialari}}, {{W-|eu|historiagile}}
* {{Z|be}}: {{W-|be|гісто́рык|n}}, {{W-|be|гісто́рычка|m}}
* {{Z|bg}}: {{W-|bg|истори́к|n}}, {{W-|bg|истори́чка|m}}
* {{Z|my}}: {{W-|my|သမိုင်းပညာရှင်}}
* {{Z|cs}}: {{W+|cs|historik|n}}, {{W+|cs|historička|m}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|歷史學家|tr=lìshǐxuéjiā}}, {{W+|cmn|歷史家|tr=lìshǐjiā}}, {{W+|cmn|史家|tr=shǐjiā}}
* {{Z|da}}: {{W-|da|historiker|g}}
* {{Z|ar}}: {{W-|ar|مُؤَرِّخ}}, {{W-|ar|مُؤَرِّخَة|m}}
* {{Z|hy}}: {{W+|hy|պատմագետ}}, {{W+|hy|պատմաբան}}
* {{Z|eo}}: {{W+|eo|historiisto}}
* {{Z|et}}: {{W+|et|ajaloolane}}
* {{Z|fa}}: {{W+|fa|مورخ|tr=movarrax}}
* {{Z|fi}}: {{W+|fi|historioitsija}}
* {{Z|fr}}: {{W+|fr|historien|n}}, {{W+|fr|historienne|m}}
* {{Z|gl}}: {{W+|gl|historiador|n}}
* {{Z|ka}}: {{W-|ka|ისტორიკოსი}}
* {{Z|hi}}: {{W-|hi|इतिहासकार|n}}, {{W-|hi|इतिहासज्ञ|n}}
* {{Z|nl}}: {{W+|nl|historicus|n}}, {{W+|nl|geschiedkundige|n|m}}
* {{Z|he}}: {{W+|he|הִיסְטוֹרְיוֹן|n|tr=historyón}}
* {{Z|io}}: {{W+|io|historiisto}}, {{W+|io|historiero}}
* {{Z|ang}}: {{W-|ang|stǣrleornere|n}}
* {{Z|en}}: {{W+|en|historian}}
* {{Z|ga}}: {{W-|ga|staraí|n}}
* {{Z|it}}: {{W+|it|storico|n}}, {{W+|it|storica|m}}
* {{Z|is}}: {{W+|is|sagnfræðingur|n}}
* {{Z|ja}}: {{W+|ja|歴史家|tr=れきしか, rekishika}}
* {{Z|ca}}: {{W+|ca|historiador|n}}
* {{Z|ky}}: {{W+|ky|тарыхчы}}
* {{Z|ko}}: {{W-|ko|역사학자}}, {{W-|ko|력사학자}}, {{W+|ko|역사가}}, {{W-|ko|력사가}}, {{W+|ko|사학자}}
* {{Z|la}}: {{W+|la|historicus|n}}
* {{Z|lv}}: {{W-|lv|vēsturnieks|n}}, {{W-|lv|vēsturniece|m}}
* {{Z|lt}}: {{W-|lt|istorikas|n}}, {{W-|lt|istorikė|m}}
* {{Z|mk}}: {{W-|mk|историчар|n}}, {{W-|mk|историчарка|m}}
* {{Z|hu}}: {{W+|hu|történész}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|түүхч}}
*: {{Z|mn}}: {{W-|mn|ᠲᠡᠦᠬᠡᠴᠢ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|historiker|n}}
*: {{Z|nn}}: {{W-|nn|historikar|n}}
* {{Z|ug}}: {{W-|ug|تارىخچى}}
* {{Z|uz}}: {{W+|uz|tarixchi}}, {{W+|uz|muarrix}}
* {{Z|pl}}: {{W+|pl|historyk|n}}, {{W+|pl|historyczka|m}}
* {{Z|pt}}: {{W+|pt|historiador|n}}, {{W+|pt|historiadora|m}}
* {{Z|kk}}: {{W-|kk|тарихшы}}
* {{Z|ro}}: {{W+|ro|istoric|n}}, {{W-|ro|istorică|m}}
* {{Z|ru}}: {{W+|ru|исто́рик|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|по̏вјеснича̄р|n}}, {{W-|sh|по̏вјеснича̄рка|m}}, {{W-|sh|хисто̀рича̄р|n}}, {{W-|sh|хисто̀рича̄рка|m}}
*: Latînî: {{W+|sh|pȍvjesničār|n}}, {{W+|sh|pȍvjesničārka|m}}, {{W+|sh|històričār|n}}, {{W+|sh|històričārka|m}}
* {{Z|sk}}: {{W+|sk|historik|n}}, {{W+|sk|historička|m}}
* {{Z|sl}}: {{W+|sl|zgodovinar|n}}, {{W-|sl|zgodovinarka|m}}
* {{Z|es}}: {{W+|es|historiador|n}}, {{W+|es|historiadora|m}}
* {{Z|sw}}: {{W-|sw|mwanahistoria}}
* {{Z|sv}}: {{W+|sv|historiker|g}}
* {{Z|tg}}: {{W-|tg|муаррих}}
* {{Z|th}}: {{W-|th|นักประวัติศาสตร์}}
* {{Z|tt}}: {{W+|tt|тарихчы}}
* {{Z|tr}}: {{W+|tr|tarihçi}}
* {{Z|tk}}: {{W-|tk|taryhçy}}
* {{Z|ur}}: {{W-|ur|مؤرخ|n|tr=muvarrix}}
* {{Z|uk}}: {{W-|uk|істо́рик|n}}, {{W-|uk|істо́ричка|m}}
* {{Z|vi}}: {{W+|vi|[[nhà]] [[sử học]]}}, {{W+|vi|sử gia}}
* {{Z|vro}}: {{W-|vro|aoluulanõ}}
* {{Z|cy}}: {{W+|cy|hanesydd|n}}
* {{Z|el}}: {{W+|el|ιστορικός|n}}
*: {{Z|grc}}: {{W-|grc|ἱστορικός|n}}
{{werger-bin}}
:{{werger çavkanî|en|historian|beş=Ev qismê Wergerê|oldid=90059179}}
=== Çavkanî ===
{{çavkanî}}
fx6o6c7141kyrfwez8hs2qcxdg96cb9
6334308
6334307
2026-08-17T12:49:20Z
Wikihez
7344
6334308
wikitext
text/x-wiki
{{bnr-h|tarixçi}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:ჩობან_აღა.png|thumb|{{w|Şerefxanê Bidlîsî}} bi navê tarîxçiya kurd a ewil tê bibîranîn.]]
{{navdêr|ku|c=mn}}
# {{e|ku|meslek}} Kesê ku li ser [[tarîx]]ê dinivîse an lê dikole.<ref>{{Ç:Jaba:1879|تاریخچی<ts:tarikhtchi><sc:Arab>|r=92}}</ref>
#: {{hevmane|ku|tarîxnas|dîroknas|dîrokvan|dîrokzan|mêjûnas|mêjûzan}}
#* {{jêder-kovar|ku|jêgirtin='''Tarîxçîkî''' Îngîlîz navê wî Arnold Tombey e, dibêje: "Tirk bi tarîxê nizanin, ku li wan teng bibe karin Hezretê Nebî Silêman û Qanûnî Siltan Silêman bikin yek, tenê ji navê wan."|kovar=Welat (Rojnameya Hefteyî)|sernav=TC dewletî û dewletên Tirkan yên nû|nivîskar=w:Musa Anter|hejmar=62|sal=1993|rûpel=7}}
#* {{jêder-kovar|ku|jêgirtin=Malzemeyê dîrokzan (yan jî dîroknas – zimanzanên kurd(!) hêj biryar nedane ku ew ji ”'''tarîxçî'''”yan re bibêjin dîrokzan yan dîroknas.) belgeyên rastîn û şîroveyên îlmî, zanîstî ne.|kovar=Agirî|sernav=Edebiyat û Dîrok|nivîskar=w:Loqman Polat|hejmar=61|sal=2007|rûpel=11}}
#* {{jêder-malper|ku|jêgirtin='''Tarîxçî''' û teorîsyenên Tirk, Ziya Gökalpê Diyarbekirî hosteyê ilmê xwe yê sosyolojîyê dihesibînin.|nivîskar=w:Omer Faruk Baran|sernav=Zewaca Ziya Gökalp û sosyolojîyeka sînesoj|url=https://www.botantimes.com/zewaca-ziya-gokalp-u-sosyolojiyeka-sinesoj/|malper=Botan Times|sal=2023}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Etîmolojî ====
Deyn ji {{deyn|ota|ku|tr=tarihçi}}. {{anlz|ku|tarîx|-çî}}.
==== Werger ====
{{werger-ser|kesê ku li ser tarîxê dinivîse an lê dikole}}
* {{Z|sq}}: {{W+|sq|historian}}
* {{Z|de}}: {{W+|de|Historiker|n}}, {{W+|de|Historikerin|m}}
* {{Z|az}}: {{W+|az|tarixçi}}
* {{Z|eu}}: {{W-|eu|historialari}}, {{W-|eu|historiagile}}
* {{Z|be}}: {{W-|be|гісто́рык|n}}, {{W-|be|гісто́рычка|m}}
* {{Z|bg}}: {{W-|bg|истори́к|n}}, {{W-|bg|истори́чка|m}}
* {{Z|my}}: {{W-|my|သမိုင်းပညာရှင်}}
* {{Z|cs}}: {{W+|cs|historik|n}}, {{W+|cs|historička|m}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|歷史學家|tr=lìshǐxuéjiā}}, {{W+|cmn|歷史家|tr=lìshǐjiā}}, {{W+|cmn|史家|tr=shǐjiā}}
* {{Z|da}}: {{W-|da|historiker|g}}
* {{Z|ar}}: {{W-|ar|مُؤَرِّخ}}, {{W-|ar|مُؤَرِّخَة|m}}
* {{Z|hy}}: {{W+|hy|պատմագետ}}, {{W+|hy|պատմաբան}}
* {{Z|eo}}: {{W+|eo|historiisto}}
* {{Z|et}}: {{W+|et|ajaloolane}}
* {{Z|fa}}: {{W+|fa|مورخ|tr=movarrax}}
* {{Z|fi}}: {{W+|fi|historioitsija}}
* {{Z|fr}}: {{W+|fr|historien|n}}, {{W+|fr|historienne|m}}
* {{Z|gl}}: {{W+|gl|historiador|n}}
* {{Z|ka}}: {{W-|ka|ისტორიკოსი}}
* {{Z|hi}}: {{W-|hi|इतिहासकार|n}}, {{W-|hi|इतिहासज्ञ|n}}
* {{Z|nl}}: {{W+|nl|historicus|n}}, {{W+|nl|geschiedkundige|n|m}}
* {{Z|he}}: {{W+|he|הִיסְטוֹרְיוֹן|n|tr=historyón}}
* {{Z|io}}: {{W+|io|historiisto}}, {{W+|io|historiero}}
* {{Z|ang}}: {{W-|ang|stǣrleornere|n}}
* {{Z|en}}: {{W+|en|historian}}
* {{Z|ga}}: {{W-|ga|staraí|n}}
* {{Z|it}}: {{W+|it|storico|n}}, {{W+|it|storica|m}}
* {{Z|is}}: {{W+|is|sagnfræðingur|n}}
* {{Z|ja}}: {{W+|ja|歴史家|tr=れきしか, rekishika}}
* {{Z|ca}}: {{W+|ca|historiador|n}}
* {{Z|ky}}: {{W+|ky|тарыхчы}}
* {{Z|ko}}: {{W-|ko|역사학자}}, {{W-|ko|력사학자}}, {{W+|ko|역사가}}, {{W-|ko|력사가}}, {{W+|ko|사학자}}
* {{Z|la}}: {{W+|la|historicus|n}}
* {{Z|lv}}: {{W-|lv|vēsturnieks|n}}, {{W-|lv|vēsturniece|m}}
* {{Z|lt}}: {{W-|lt|istorikas|n}}, {{W-|lt|istorikė|m}}
* {{Z|mk}}: {{W-|mk|историчар|n}}, {{W-|mk|историчарка|m}}
* {{Z|hu}}: {{W+|hu|történész}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|түүхч}}
*: {{Z|mn}}: {{W-|mn|ᠲᠡᠦᠬᠡᠴᠢ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|historiker|n}}
*: {{Z|nn}}: {{W-|nn|historikar|n}}
* {{Z|ug}}: {{W-|ug|تارىخچى}}
* {{Z|uz}}: {{W+|uz|tarixchi}}, {{W+|uz|muarrix}}
* {{Z|pl}}: {{W+|pl|historyk|n}}, {{W+|pl|historyczka|m}}
* {{Z|pt}}: {{W+|pt|historiador|n}}, {{W+|pt|historiadora|m}}
* {{Z|kk}}: {{W-|kk|тарихшы}}
* {{Z|ro}}: {{W+|ro|istoric|n}}, {{W-|ro|istorică|m}}
* {{Z|ru}}: {{W+|ru|исто́рик|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|по̏вјеснича̄р|n}}, {{W-|sh|по̏вјеснича̄рка|m}}, {{W-|sh|хисто̀рича̄р|n}}, {{W-|sh|хисто̀рича̄рка|m}}
*: Latînî: {{W+|sh|pȍvjesničār|n}}, {{W+|sh|pȍvjesničārka|m}}, {{W+|sh|històričār|n}}, {{W+|sh|històričārka|m}}
* {{Z|sk}}: {{W+|sk|historik|n}}, {{W+|sk|historička|m}}
* {{Z|sl}}: {{W+|sl|zgodovinar|n}}, {{W-|sl|zgodovinarka|m}}
* {{Z|es}}: {{W+|es|historiador|n}}, {{W+|es|historiadora|m}}
* {{Z|sw}}: {{W-|sw|mwanahistoria}}
* {{Z|sv}}: {{W+|sv|historiker|g}}
* {{Z|tg}}: {{W-|tg|муаррих}}
* {{Z|th}}: {{W-|th|นักประวัติศาสตร์}}
* {{Z|tt}}: {{W+|tt|тарихчы}}
* {{Z|tr}}: {{W+|tr|tarihçi}}
* {{Z|tk}}: {{W-|tk|taryhçy}}
* {{Z|ur}}: {{W-|ur|مؤرخ|n|tr=muvarrix}}
* {{Z|uk}}: {{W-|uk|істо́рик|n}}, {{W-|uk|істо́ричка|m}}
* {{Z|vi}}: {{W+|vi|[[nhà]] [[sử học]]}}, {{W+|vi|sử gia}}
* {{Z|vro}}: {{W-|vro|aoluulanõ}}
* {{Z|cy}}: {{W+|cy|hanesydd|n}}
* {{Z|el}}: {{W+|el|ιστορικός|n}}
*: {{Z|grc}}: {{W-|grc|ἱστορικός|n}}
{{werger-bin}}
:{{werger çavkanî|en|historian|beş=Ev qismê Wergerê|oldid=90059179}}
=== Çavkanî ===
{{çavkanî}}
hm3xnm9mxsfe0g7m4a2wpjx4zifg2g1
6334309
6334308
2026-08-17T12:49:42Z
Wikihez
7344
6334309
wikitext
text/x-wiki
{{bnr-h|tarixçi}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:ჩობან_აღა.png|thumb|200px|{{w|Şerefxanê Bidlîsî}} bi navê tarîxçiya kurd a ewil tê bibîranîn.]]
{{navdêr|ku|c=mn}}
# {{e|ku|meslek}} Kesê ku li ser [[tarîx]]ê dinivîse an lê dikole.<ref>{{Ç:Jaba:1879|تاریخچی<ts:tarikhtchi><sc:Arab>|r=92}}</ref>
#: {{hevmane|ku|tarîxnas|dîroknas|dîrokvan|dîrokzan|mêjûnas|mêjûzan}}
#* {{jêder-kovar|ku|jêgirtin='''Tarîxçîkî''' Îngîlîz navê wî Arnold Tombey e, dibêje: "Tirk bi tarîxê nizanin, ku li wan teng bibe karin Hezretê Nebî Silêman û Qanûnî Siltan Silêman bikin yek, tenê ji navê wan."|kovar=Welat (Rojnameya Hefteyî)|sernav=TC dewletî û dewletên Tirkan yên nû|nivîskar=w:Musa Anter|hejmar=62|sal=1993|rûpel=7}}
#* {{jêder-kovar|ku|jêgirtin=Malzemeyê dîrokzan (yan jî dîroknas – zimanzanên kurd(!) hêj biryar nedane ku ew ji ”'''tarîxçî'''”yan re bibêjin dîrokzan yan dîroknas.) belgeyên rastîn û şîroveyên îlmî, zanîstî ne.|kovar=Agirî|sernav=Edebiyat û Dîrok|nivîskar=w:Loqman Polat|hejmar=61|sal=2007|rûpel=11}}
#* {{jêder-malper|ku|jêgirtin='''Tarîxçî''' û teorîsyenên Tirk, Ziya Gökalpê Diyarbekirî hosteyê ilmê xwe yê sosyolojîyê dihesibînin.|nivîskar=w:Omer Faruk Baran|sernav=Zewaca Ziya Gökalp û sosyolojîyeka sînesoj|url=https://www.botantimes.com/zewaca-ziya-gokalp-u-sosyolojiyeka-sinesoj/|malper=Botan Times|sal=2023}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Etîmolojî ====
Deyn ji {{deyn|ota|ku|tr=tarihçi}}. {{anlz|ku|tarîx|-çî}}.
==== Werger ====
{{werger-ser|kesê ku li ser tarîxê dinivîse an lê dikole}}
* {{Z|sq}}: {{W+|sq|historian}}
* {{Z|de}}: {{W+|de|Historiker|n}}, {{W+|de|Historikerin|m}}
* {{Z|az}}: {{W+|az|tarixçi}}
* {{Z|eu}}: {{W-|eu|historialari}}, {{W-|eu|historiagile}}
* {{Z|be}}: {{W-|be|гісто́рык|n}}, {{W-|be|гісто́рычка|m}}
* {{Z|bg}}: {{W-|bg|истори́к|n}}, {{W-|bg|истори́чка|m}}
* {{Z|my}}: {{W-|my|သမိုင်းပညာရှင်}}
* {{Z|cs}}: {{W+|cs|historik|n}}, {{W+|cs|historička|m}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|歷史學家|tr=lìshǐxuéjiā}}, {{W+|cmn|歷史家|tr=lìshǐjiā}}, {{W+|cmn|史家|tr=shǐjiā}}
* {{Z|da}}: {{W-|da|historiker|g}}
* {{Z|ar}}: {{W-|ar|مُؤَرِّخ}}, {{W-|ar|مُؤَرِّخَة|m}}
* {{Z|hy}}: {{W+|hy|պատմագետ}}, {{W+|hy|պատմաբան}}
* {{Z|eo}}: {{W+|eo|historiisto}}
* {{Z|et}}: {{W+|et|ajaloolane}}
* {{Z|fa}}: {{W+|fa|مورخ|tr=movarrax}}
* {{Z|fi}}: {{W+|fi|historioitsija}}
* {{Z|fr}}: {{W+|fr|historien|n}}, {{W+|fr|historienne|m}}
* {{Z|gl}}: {{W+|gl|historiador|n}}
* {{Z|ka}}: {{W-|ka|ისტორიკოსი}}
* {{Z|hi}}: {{W-|hi|इतिहासकार|n}}, {{W-|hi|इतिहासज्ञ|n}}
* {{Z|nl}}: {{W+|nl|historicus|n}}, {{W+|nl|geschiedkundige|n|m}}
* {{Z|he}}: {{W+|he|הִיסְטוֹרְיוֹן|n|tr=historyón}}
* {{Z|io}}: {{W+|io|historiisto}}, {{W+|io|historiero}}
* {{Z|ang}}: {{W-|ang|stǣrleornere|n}}
* {{Z|en}}: {{W+|en|historian}}
* {{Z|ga}}: {{W-|ga|staraí|n}}
* {{Z|it}}: {{W+|it|storico|n}}, {{W+|it|storica|m}}
* {{Z|is}}: {{W+|is|sagnfræðingur|n}}
* {{Z|ja}}: {{W+|ja|歴史家|tr=れきしか, rekishika}}
* {{Z|ca}}: {{W+|ca|historiador|n}}
* {{Z|ky}}: {{W+|ky|тарыхчы}}
* {{Z|ko}}: {{W-|ko|역사학자}}, {{W-|ko|력사학자}}, {{W+|ko|역사가}}, {{W-|ko|력사가}}, {{W+|ko|사학자}}
* {{Z|la}}: {{W+|la|historicus|n}}
* {{Z|lv}}: {{W-|lv|vēsturnieks|n}}, {{W-|lv|vēsturniece|m}}
* {{Z|lt}}: {{W-|lt|istorikas|n}}, {{W-|lt|istorikė|m}}
* {{Z|mk}}: {{W-|mk|историчар|n}}, {{W-|mk|историчарка|m}}
* {{Z|hu}}: {{W+|hu|történész}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|түүхч}}
*: {{Z|mn}}: {{W-|mn|ᠲᠡᠦᠬᠡᠴᠢ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|historiker|n}}
*: {{Z|nn}}: {{W-|nn|historikar|n}}
* {{Z|ug}}: {{W-|ug|تارىخچى}}
* {{Z|uz}}: {{W+|uz|tarixchi}}, {{W+|uz|muarrix}}
* {{Z|pl}}: {{W+|pl|historyk|n}}, {{W+|pl|historyczka|m}}
* {{Z|pt}}: {{W+|pt|historiador|n}}, {{W+|pt|historiadora|m}}
* {{Z|kk}}: {{W-|kk|тарихшы}}
* {{Z|ro}}: {{W+|ro|istoric|n}}, {{W-|ro|istorică|m}}
* {{Z|ru}}: {{W+|ru|исто́рик|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|по̏вјеснича̄р|n}}, {{W-|sh|по̏вјеснича̄рка|m}}, {{W-|sh|хисто̀рича̄р|n}}, {{W-|sh|хисто̀рича̄рка|m}}
*: Latînî: {{W+|sh|pȍvjesničār|n}}, {{W+|sh|pȍvjesničārka|m}}, {{W+|sh|històričār|n}}, {{W+|sh|històričārka|m}}
* {{Z|sk}}: {{W+|sk|historik|n}}, {{W+|sk|historička|m}}
* {{Z|sl}}: {{W+|sl|zgodovinar|n}}, {{W-|sl|zgodovinarka|m}}
* {{Z|es}}: {{W+|es|historiador|n}}, {{W+|es|historiadora|m}}
* {{Z|sw}}: {{W-|sw|mwanahistoria}}
* {{Z|sv}}: {{W+|sv|historiker|g}}
* {{Z|tg}}: {{W-|tg|муаррих}}
* {{Z|th}}: {{W-|th|นักประวัติศาสตร์}}
* {{Z|tt}}: {{W+|tt|тарихчы}}
* {{Z|tr}}: {{W+|tr|tarihçi}}
* {{Z|tk}}: {{W-|tk|taryhçy}}
* {{Z|ur}}: {{W-|ur|مؤرخ|n|tr=muvarrix}}
* {{Z|uk}}: {{W-|uk|істо́рик|n}}, {{W-|uk|істо́ричка|m}}
* {{Z|vi}}: {{W+|vi|[[nhà]] [[sử học]]}}, {{W+|vi|sử gia}}
* {{Z|vro}}: {{W-|vro|aoluulanõ}}
* {{Z|cy}}: {{W+|cy|hanesydd|n}}
* {{Z|el}}: {{W+|el|ιστορικός|n}}
*: {{Z|grc}}: {{W-|grc|ἱστορικός|n}}
{{werger-bin}}
:{{werger çavkanî|en|historian|beş=Ev qismê Wergerê|oldid=90059179}}
=== Çavkanî ===
{{çavkanî}}
5mrkbm9fusq749yi2np0xjoao4h7r80
6334310
6334309
2026-08-17T12:52:25Z
Wikihez
7344
6334310
wikitext
text/x-wiki
{{bnr-h|tarixçi}}
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:ჩობან_აღა.png|thumb|upright=1|{{w|Şerefxanê Bidlîsî}} bi navê '''tarîxçiya''' kurd a ewil tê bibîranîn.]]
{{navdêr|ku|c=mn}}
# {{e|ku|meslek}} Kesê ku li ser [[tarîx]]ê dinivîse an lê dikole.<ref>{{Ç:Jaba:1879|تاریخچی<ts:tarikhtchi><sc:Arab>|r=92}}</ref>
#: {{hevmane|ku|tarîxnas|dîroknas|dîrokvan|dîrokzan|mêjûnas|mêjûzan}}
#* {{jêder-kovar|ku|jêgirtin='''Tarîxçîkî''' Îngîlîz navê wî Arnold Tombey e, dibêje: "Tirk bi tarîxê nizanin, ku li wan teng bibe karin Hezretê Nebî Silêman û Qanûnî Siltan Silêman bikin yek, tenê ji navê wan."|kovar=Welat (Rojnameya Hefteyî)|sernav=TC dewletî û dewletên Tirkan yên nû|nivîskar=w:Musa Anter|hejmar=62|sal=1993|rûpel=7}}
#* {{jêder-kovar|ku|jêgirtin=Malzemeyê dîrokzan (yan jî dîroknas – zimanzanên kurd(!) hêj biryar nedane ku ew ji ”'''tarîxçî'''”yan re bibêjin dîrokzan yan dîroknas.) belgeyên rastîn û şîroveyên îlmî, zanîstî ne.|kovar=Agirî|sernav=Edebiyat û Dîrok|nivîskar=w:Loqman Polat|hejmar=61|sal=2007|rûpel=11}}
#* {{jêder-malper|ku|jêgirtin='''Tarîxçî''' û teorîsyenên Tirk, Ziya Gökalpê Diyarbekirî hosteyê ilmê xwe yê sosyolojîyê dihesibînin.|nivîskar=w:Omer Faruk Baran|sernav=Zewaca Ziya Gökalp û sosyolojîyeka sînesoj|url=https://www.botantimes.com/zewaca-ziya-gokalp-u-sosyolojiyeka-sinesoj/|malper=Botan Times|sal=2023}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Etîmolojî ====
Deyn ji {{deyn|ota|ku|tr=tarihçi}}. {{anlz|ku|tarîx|-çî}}.
==== Bêhtir bixwîne ====
* {{wpedî}}
==== Werger ====
{{werger-ser|kesê ku li ser tarîxê dinivîse an lê dikole}}
* {{Z|sq}}: {{W+|sq|historian}}
* {{Z|de}}: {{W+|de|Historiker|n}}, {{W+|de|Historikerin|m}}
* {{Z|az}}: {{W+|az|tarixçi}}
* {{Z|eu}}: {{W-|eu|historialari}}, {{W-|eu|historiagile}}
* {{Z|be}}: {{W-|be|гісто́рык|n}}, {{W-|be|гісто́рычка|m}}
* {{Z|bg}}: {{W-|bg|истори́к|n}}, {{W-|bg|истори́чка|m}}
* {{Z|my}}: {{W-|my|သမိုင်းပညာရှင်}}
* {{Z|cs}}: {{W+|cs|historik|n}}, {{W+|cs|historička|m}}
* {{Z|zh}}:
*: {{Z|cmn}}: {{W+|cmn|歷史學家|tr=lìshǐxuéjiā}}, {{W+|cmn|歷史家|tr=lìshǐjiā}}, {{W+|cmn|史家|tr=shǐjiā}}
* {{Z|da}}: {{W-|da|historiker|g}}
* {{Z|ar}}: {{W-|ar|مُؤَرِّخ}}, {{W-|ar|مُؤَرِّخَة|m}}
* {{Z|hy}}: {{W+|hy|պատմագետ}}, {{W+|hy|պատմաբան}}
* {{Z|eo}}: {{W+|eo|historiisto}}
* {{Z|et}}: {{W+|et|ajaloolane}}
* {{Z|fa}}: {{W+|fa|مورخ|tr=movarrax}}
* {{Z|fi}}: {{W+|fi|historioitsija}}
* {{Z|fr}}: {{W+|fr|historien|n}}, {{W+|fr|historienne|m}}
* {{Z|gl}}: {{W+|gl|historiador|n}}
* {{Z|ka}}: {{W-|ka|ისტორიკოსი}}
* {{Z|hi}}: {{W-|hi|इतिहासकार|n}}, {{W-|hi|इतिहासज्ञ|n}}
* {{Z|nl}}: {{W+|nl|historicus|n}}, {{W+|nl|geschiedkundige|n|m}}
* {{Z|he}}: {{W+|he|הִיסְטוֹרְיוֹן|n|tr=historyón}}
* {{Z|io}}: {{W+|io|historiisto}}, {{W+|io|historiero}}
* {{Z|ang}}: {{W-|ang|stǣrleornere|n}}
* {{Z|en}}: {{W+|en|historian}}
* {{Z|ga}}: {{W-|ga|staraí|n}}
* {{Z|it}}: {{W+|it|storico|n}}, {{W+|it|storica|m}}
* {{Z|is}}: {{W+|is|sagnfræðingur|n}}
* {{Z|ja}}: {{W+|ja|歴史家|tr=れきしか, rekishika}}
* {{Z|ca}}: {{W+|ca|historiador|n}}
* {{Z|ky}}: {{W+|ky|тарыхчы}}
* {{Z|ko}}: {{W-|ko|역사학자}}, {{W-|ko|력사학자}}, {{W+|ko|역사가}}, {{W-|ko|력사가}}, {{W+|ko|사학자}}
* {{Z|la}}: {{W+|la|historicus|n}}
* {{Z|lv}}: {{W-|lv|vēsturnieks|n}}, {{W-|lv|vēsturniece|m}}
* {{Z|lt}}: {{W-|lt|istorikas|n}}, {{W-|lt|istorikė|m}}
* {{Z|mk}}: {{W-|mk|историчар|n}}, {{W-|mk|историчарка|m}}
* {{Z|hu}}: {{W+|hu|történész}}
* {{Z|mn}}:
*: Kirîlî: {{W+|mn|түүхч}}
*: {{Z|mn}}: {{W-|mn|ᠲᠡᠦᠬᠡᠴᠢ}}
* {{Z|no}}:
*: {{Z|nb}}: {{W-|nb|historiker|n}}
*: {{Z|nn}}: {{W-|nn|historikar|n}}
* {{Z|ug}}: {{W-|ug|تارىخچى}}
* {{Z|uz}}: {{W+|uz|tarixchi}}, {{W+|uz|muarrix}}
* {{Z|pl}}: {{W+|pl|historyk|n}}, {{W+|pl|historyczka|m}}
* {{Z|pt}}: {{W+|pt|historiador|n}}, {{W+|pt|historiadora|m}}
* {{Z|kk}}: {{W-|kk|тарихшы}}
* {{Z|ro}}: {{W+|ro|istoric|n}}, {{W-|ro|istorică|m}}
* {{Z|ru}}: {{W+|ru|исто́рик|n}}
* {{Z|sh}}:
*: Kirîlî: {{W-|sh|по̏вјеснича̄р|n}}, {{W-|sh|по̏вјеснича̄рка|m}}, {{W-|sh|хисто̀рича̄р|n}}, {{W-|sh|хисто̀рича̄рка|m}}
*: Latînî: {{W+|sh|pȍvjesničār|n}}, {{W+|sh|pȍvjesničārka|m}}, {{W+|sh|històričār|n}}, {{W+|sh|històričārka|m}}
* {{Z|sk}}: {{W+|sk|historik|n}}, {{W+|sk|historička|m}}
* {{Z|sl}}: {{W+|sl|zgodovinar|n}}, {{W-|sl|zgodovinarka|m}}
* {{Z|es}}: {{W+|es|historiador|n}}, {{W+|es|historiadora|m}}
* {{Z|sw}}: {{W-|sw|mwanahistoria}}
* {{Z|sv}}: {{W+|sv|historiker|g}}
* {{Z|tg}}: {{W-|tg|муаррих}}
* {{Z|th}}: {{W-|th|นักประวัติศาสตร์}}
* {{Z|tt}}: {{W+|tt|тарихчы}}
* {{Z|tr}}: {{W+|tr|tarihçi}}
* {{Z|tk}}: {{W-|tk|taryhçy}}
* {{Z|ur}}: {{W-|ur|مؤرخ|n|tr=muvarrix}}
* {{Z|uk}}: {{W-|uk|істо́рик|n}}, {{W-|uk|істо́ричка|m}}
* {{Z|vi}}: {{W+|vi|[[nhà]] [[sử học]]}}, {{W+|vi|sử gia}}
* {{Z|vro}}: {{W-|vro|aoluulanõ}}
* {{Z|cy}}: {{W+|cy|hanesydd|n}}
* {{Z|el}}: {{W+|el|ιστορικός|n}}
*: {{Z|grc}}: {{W-|grc|ἱστορικός|n}}
{{werger-bin}}
:{{werger çavkanî|en|historian|beş=Ev qismê Wergerê|oldid=90059179}}
=== Çavkanî ===
{{çavkanî}}
qj570mzbvn8cj2s4ydyrivyzvbnarsp
Şablon:TemplateData header
10
1357716
6334352
6265293
2026-08-17T15:37:29Z
Wikihez
7344
6334352
wikitext
text/x-wiki
<div class="templatedata-header">{{#if:{{{noheader|}}}| |Ev ji bo vê şablonê belgekirina {{#if:{{{nolink|}}}|TemplateDatayê|[[mw:Help:TemplateData|TemplateDatayê]]}} ye ku ji hêla [[mw:VisualEditor|GuhartinaDîtbarî]] û amûrên din ve tê bikaranîn.}}
'''TemplateData ji bo {{{1|{{BASEPAGENAME}}}}}'''
</div>
<includeonly>{{#ifeq:{{SUBPAGENAME}}|ceribandin||
[[Category:Şablonên bi belgekirina TemplateDatayê]]
}}</includeonly>
<noinclude>{{documentation}}</noinclude>
7crzhpmy81tbv87b5ndpos95o5o5o45
Kategorî:Şablonên bi belgekirina TemplateDatayê
14
1357720
6334353
6241425
2026-08-17T15:38:20Z
Wikihez
7344
Wikihez rûpela [[Kategorî:Şablonên belgekirinê]] bêyî beralîkirinek bar kir [[Kategorî:Şablonên bi belgekirina TemplateDatayê]]
6241425
wikitext
text/x-wiki
Şablonên ku TemplateData bi kar tînin yên ku tê de {{şx|TemplateData header}} hene dikevin vê kategoriyê.
[[Kategorî:Şablon|belge]]
12v6t0h2j753vicvo072luv21ja48wc
Wîkîferheng:Xebat
4
1357857
6334364
6334301
2026-08-17T16:18:49Z
Wikihez
7344
/* MediaWiki:Gadget-start.js */ Bersiv
6334364
wikitext
text/x-wiki
{{/ser}}
= Karên ferhengî =
== Mane ==
* [[:Kategorî:Madeyên bê beşa mane]]
** [[:Kategorî:Madeyên bê beşa mane bi kurmancî]]
Li ser van rûpelan ev made hene ku maneyan wan nehatine nivîsin û divê werin nivîsin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Etîmolojî ==
*[[:Kategorî:Daxwazên etîmolojiyê]]
**[[:Kategorî:Daxwazên etîmolojiyê bi kurmancî]]
Li ser van rûpelan ev made hene ku etîmolojiya wan hîn nehatine nivîsin. Ji bo alîkariyê binêre [[WF:Etîmolojî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Cureyê rêzimanî ==
* [[:Kategorî:Gotarên bê kategoriya rêzimanî]]
* [[:Kategorî:Gotarên bê kategoriya rêzimanî bi kurmancî]]
Li ser van rûpelan ev made hene ku cureyên wan yên rêzimanî hîn nehatine nivîsin. Ji bo alîkariyê binêre [[Alîkarî:Cureyên peyvan]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Kontrol ==
* [[:Kategorî:Kontrol]]
** [[:Kategorî:Kontrola maneyê]]
Ev madde bi botê hatine çêkirin, yek bi yek divê werin kontrolkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
= Şablon =
== IPA ==
* {{ş|IPA}} divê [[Modul:IPA]] bi kar bîne. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Deng ==
Kurdîkirina belgekirina {{ş|deng}} lazim e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 19 tîrmeh 2026 (UTC)
= Modul =
== Modul:form of/templates ==
* [[Modul:form of/templates]]: Divê ev modul were rojanekirin û xeletiyên berê wekî ([[hevalê]]) werin rastkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:Merheba @[[Bikarhêner:Ghybu|Ghybu]] Te gotibû di vê modulê de xeletî hene, jkx tu dikarî li vir binivîsî çi kêmasî hene, daku wextê rojane dikim ez ji bîr nekim? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:16, 21 tîrmeh 2026 (UTC)
*::Silav. Nizanim ji bîra min çûye... Herwiha ji bo çêkirina formên navdêran zanînên cîns û tîpa dawî bes e:
*::* (-ê, mê) => çemandî, bangkirin
*::* (-ê, nêr) => îzafe
*::* (-ê, mê û nêr) => îzafe (nêr), bangkirin (mê), çemandî (mê)
*::* (-a) => îzafe (mê)
*::* (-ekî) => îzafe û çemandî (nêr, nebinavkirî)
*::* (-eke) = > îzafe (mê, nebinavkirî)
*::* ...
*:: <code>if find(mw.title.getCurrentTitle().subpageText, 'ekî$') then ... end</code>
*::<code><nowiki>{{ku-tewîn-navForm|<navdêr>|<zayend>}</nowiki></code>
*::Tablo û nivîs di yekcarî de tên çêkirin...
*::Feqet problem lêzêdekirina mînakan e. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:34, 21 tîrmeh 2026 (UTC)
*:::Lêzêdekirina kîjan nimûneyan? Ev koda js ji bo çi ye? Qisûrê nenêre min fêm nekir :/ [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:13, 21 tîrmeh 2026 (UTC)
*::::Li vira ([[hevalê#Formeke navdêrê]]) di bin maneyên 1. û 2. mînak nayên tevlî kirin :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 21 tîrmeh 2026 (UTC)
*:::::Ev tercîh e, ne mecbûrî ye, em dikarin [https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&diff=prev&oldid=6276350 wisa] bikin. Wekî din? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:29, 21 tîrmeh 2026 (UTC)
*::::::Min li jor tênê got ku tenê parametreyên "navdêr" û "zayend" hewce nee. Bi '''[//ku.wiktionary.org/w/index.php?title=heval%C3%AA&curid=969734&diff=6276353&oldid=6276350 vî]''' awayî jî tê çêkirin. Parametreyên din ne hewce ne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:38, 21 tîrmeh 2026 (UTC)
*:::::::Ji bo wê şabloneke din lazim e, bila ev şablon wekî îngilîzî bimîne em ji bo kurmancî yekî din çêkin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:42, 21 tîrmeh 2026 (UTC)
*::::::::[https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&oldid=6276885 wisa] çawa ye? Bi min nivîsîna nimûneyan zêde ne lazim e ji ber ku ti kes serê xwe pê naêşîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:46, 23 tîrmeh 2026 (UTC)
*:::::::::@[[Bikarhêner:Ghybu|Ghybu]] Vê şablonê bi kar bînin an na? Navê şablonê kê çi be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:07, 24 tîrmeh 2026 (UTC)
== Modul:columns ==
* [[Modul:columns]] were rojanekirin û {{ş|kol3}} bila lînka ''nîşan bide'' deyne beriya sernavê, kolê di qutiya rengî de bila nebe. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:etymon ==
* [[Modul:etymon kevn]]: Rojanekirin hewce ye. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Modul:ziman/code to canonical name]] ==
Belgekirina skrîptê û js divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:31, 22 tîrmeh 2026 (UTC)
:* [[MediaWiki:UpdateLanguageNameAndCode.js]]
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:50, 22 tîrmeh 2026 (UTC)
::* [[Modul:ziman/print]]
::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:52, 22 tîrmeh 2026 (UTC)
:::*[[Modul:ziman/şablon]]
:::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:56, 22 tîrmeh 2026 (UTC)
::::* [[MediaWiki:Gadget-translation editor.js/ziman.json]]
::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:06, 22 tîrmeh 2026 (UTC)
== [[Modul:quote]] ==
Piştî ev modul hate hazirkirin, lazim e navên jegirtinan werin rastkirin û li hemûyan {{param|jêgirtin}} were zêdekirin. Bnr. [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Mem û Zîn]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 5 tebax 2026 (UTC)
::Ev modul hema bêje hazir e. Hûn li ser van madeyan çi difikirin:
# {{para|tarîx}} lazim e full date be eger na, lazim e {{para|sal}} were bikaranîn. Feqat "'''2021''' kanûna pêşîn 23" bi vî şiklî bi min ne problem e çnkî derdê en.wikt "attestation" e delîla herî pêşî jî sal e.
#: {{demo|sep=wisa xuya dibe<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|tarîx=23 December 2021|nivîskar=Harry Hobbs; George Williams|sernav=Micronations and the Search for Sovereignty|url=https://www.cambridge.org/core/books/micronations-and-the-search-for-sovereignty/B0414A3A322695D9EE71E4CDDFDA06DE|weşanxane=Cambridge University Press|rûpel=155|isbn=978-1-009-15012-5|jêgirtin=jêgirtin.|t=-}}</nowiki>}}
#{{para|amadekar}}/{{para|edîtor}}/{{para|terciman}} bi şiklê edîtoriya, amadekariya, tercimekirina
#: {{demo|sep=dide<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|sernav=Medical Surgical Nursing|serî=Juta's Manual of Nursing|cildê-seriyê=volume 4|edîtor=Sophie Mataniele Mogotlane; Joyce Mokoena; Motshedisi Eunice Chauke; Anne Young|sal=2005|weşanxane=Juta Academic|isbn=978-0702171840|rûpel=!12-29|jêgirtin=jêgirtin|t=-}}</nowiki>}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:09, 10 tebax 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Hîn gelek tişt hene divê îzah bikin û fikrê we pirs bikim, feqat îzahkirin pir zor e, em dest bi bikaranînê bikin û dûre tiştên ku hûn naxwazin em dikarin biguherînîn, dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:11, 10 tebax 2026 (UTC)
::Dibe, piştî çêbû, em di praktîkê de bibînin ku çawa ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:05, 10 tebax 2026 (UTC)
= Javascript =
== [[Mediawiki:Gadget-citoid-helper.js]] ==
Ji bo bi citoidê kopîkirina malûmatên çavkaniyan amûreke wisa lazim e. Xisûsen ji ber ku bi çend amûrên din dikare were bikaranîn.
Feqat çend problem hene, şablonên me de kêmasiya parametreyan gelek in û xerîteyên citoidê hîn nehatine çêkirin. Wana sererast bikin dûre vê amûrê çêkin, piştre [[MediaWiki:Gadget-start.js|start.js]] û [[MediaWiki:Gadget-wikt.add-examples.js|add examples.js]] were rojanekirin.
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:04, 19 tîrmeh 2026 (UTC)
:Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]], ez dibêjim karê Modul:quote zêde wext bistîne, bila em dest bi bikaranîna van amûran bikin dûre kêmasiyan em ê temam bikin înşela. Dikarî vana li cihên xwe yên rast kopî paste bikî?
# [[Special:PermanentLink/6291574|Bikarhêner:Wikihez/skrîpt/js/Gadget-citoid-helper.js]] --> [[Mediawiki:Gadget-citoid-helper.js]]
# Ya li jêr kopî bike qismê herî dawî ya AmûrênVeşartî li [[MediaWiki:Gadgets-definition]]:
## <code><nowiki>* citoid-helper[ResourceLoader|hidden|default|dependencies=mediawiki.util]|citoid-helper.js</nowiki></code>
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:58, 26 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-start.js]] ==
Rojanekirin hewce ye. Min gelek tişt lê zêde kir lê ez niha test dikime. Piştre em bar bikin:
* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 17 tîrmeh 2026 (UTC)
:Min [[MediaWiki:Gadget-start/data.json|data.json]] çêkir ji bo ku bi hêsanî malûmatên heyî werin guhartin bêyî guhartina Javascriptê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:49, 18 tîrmeh 2026 (UTC)
:Xeletiyên ku ji bîr nekim rast bikim:
:* Wextê min çavkanî "kitêb" hilbijart û bi destî tije kir xeletiyeke [https://ku.wiktionary.org/w/index.php?title=xezete&diff=prev&oldid=6265392 wisa] derket her kelîme hate lînkkirin.
:* <code>html("⠿")</code> ev ne lazim e.
:* textarea ji bo çavkaniyên jêgirtinan xaseten yên sernav hîn jî ne draggable e.
:* pêşdîtina zindî checkbox wextê tê rakirin, pêşdîtin winda nabe.
:* Butona mobîlê ya vekirina formê di moda tarî de belî nabe, tenê wekî qutiyeke tarî xuya dibe.
:* Metna hişyarkirinê di guhartina rûpelên heyî de lazim e baştir bikim.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:51, 18 tîrmeh 2026 (UTC)
::Ev problem çêbûn, ez ê binêrim ka çi problemên din hene. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:15, 19 tîrmeh 2026 (UTC)
:::Çêke: Ji bo qismê çavkaniyê yek dikare API call û Kategorî:Çavkanî bi kar bîne ji bo ku şablon zêde bikî ji yên heyî. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 19 tîrmeh 2026 (UTC)
::::Ev jî çêbû, min beşa çavkanî jî çêkir. Eger xeletî dernekeve û problema valahiya piştî etîketê ji bîr nekim, piştî rojanekirina amûrên linkify, û citoid em dikarin bar bikin. Ez ê ti tiştê nû îlawe nekim, zêde dirêj bû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:14, 21 tîrmeh 2026 (UTC)
:::::Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]]. Dikarî wana bar bikî?
:::::* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
:::::* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
:::::* [[MediaWiki:Gadgets-definition]] wisa rojane bike:
:::::* start[ResourceLoader|default|package|dependencies=mediawiki.util,ext.gadget.StorageUtils,ext.gadget.citoid-helper]|start.js|start.css|start.js/data.json
:::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:39, 5 tebax 2026 (UTC)
{{şirove}} Dewama ''[[Special:PermanentLink/6333688|Gotûbêja bikarhêner:Rewiye tine#WF:Nû]]'' bila gotûbêja wî em spam nekin. :D
{{r|Ghybu}} dikarî [[Special:PermanentLink/6333689|vê htmlê]] binêrî claude rast fêm kiriye yan na? Daxîne û wek html qeyd bike. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:43, 14 tebax 2026 (UTC)
:Min seh wîkîyên din jî kir, li wîkiya fransî tiştekî wekî daxwaza min çêkirine (tiştên ku ne hewce ne wekî "tab" çêkirine an jî hatine vêşartin): [//fr.wiktionary.org/w/index.php?title=fdsdfdsf&action=edit&redlink=1]. Formata me yê berî jî bi vê lojîkê hatibû çêkirin, wekî de.wiktionary ([//de.wiktionary.org/w/index.php?title=sdfdsfsdfs&action=edit&preload=Vorlage:Formatvorlage_(Substantiv_vereinfacht)]) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:01, 14 tebax 2026 (UTC)
::PS: Dîsa jî Claude min rind fêm kiriye :)) Feqet min got ku beşên Mane", "Bilêvkirin" û "Beşên din" cem hev (wekî "tab") ". Û dema ku tu terî di nav "Besên din" Hevmane, Jê, ... li wira werin dayîn. Wêne/Resim û Nimûne jî di nav beşa ("tab") "Mane" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:17, 14 tebax 2026 (UTC)
::Erê ya fr.wikt min jî dîtibû jixwe fikrê tab min ji wan girtibû :D (feqat min li cem wan tenê tab-a maneyê dît yên din nedît çawa tê vekirin nizanim) Berê hemû beş piştî maneyê bibûn, dûre min sê Tab çêkir, Bingehîn+Lînkkirin+Bikaranîn. Niha lazim e Tabeke din çêkim. Ya ku claude çêkiriye bnr. [[Special:PermanentLink/6333698#L-7]] ne baş e bnr. [[Special:PermanentLink/6333703|qiyaskirinê li ser vê htmlê]] Êdî ez Claude abuse dikime ji ber ku Pro ye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:26, 14 tebax 2026 (UTC)
:::Claude dibêje:
:::Good clarification — that's a real simplification, not just a relabel. Updated STEP_GROUPS from 5 tabs down to 3:
:::Mane — the definitions themselves, plus Wêne (image), now grouped in with it instead of sitting in a separate misc tab. Nimûne needed no change since it was already rendering inside each Mane sense block, not as its own step.
:::Bilêvkirin — unchanged, still just pronunciation.
:::Beşên din — everything else in one flat bucket: hevmane, jê, têkildar, herwiha binêre, formên alternatîv, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, tewandin, etîmolojî, zaravayên din, werger, and bnr-h — instead of being split across the old Bingehîn/Lînkkirin/Bikaranîn/Din tabs.
:::No DOM-order changes were needed this time — the fix from the blank-tab bug looks up the tab bar's position dynamically now, so it didn't matter that I regrouped which steps go where. Same two files, saved to your Wiki folder — worth another pass through all three tabs once you've pasted this in, same as last time. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:34, 14 tebax 2026 (UTC)
::::Mînak:
Ziman
---------------------------------------------
Cureyê rêzimanî (Navdêr)
Navdêr (hilbijêre) ┆ Cîns (hilbijêre) ┆ Jimar (hilbijêre)
- lêker -mê -yekjimar
- rengdêr -nêr -pirjimar
- ... -... -...
---------------------------------------------
Mane || Bilêvkirin || Beşên din
hilbijêre:
- mane - IPA - Hevmane
- nimûne - kîte - Dijmane
- wêne
Dema ku tu "Cureyê rêzimanî" dikî "Lêker" :
Cureyê rêzimanî (Lêker)
Lêker (hilbijêre) ┆ Cure (hilbijêre)
- navdêr -rehê niha
- rengdêr -rehê borî
- ... -...
---------------------------------------------
┆ : select/option ("tab" nîne, tê dîtin)<br>
|| : "tab"
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:45, 14 tebax 2026 (UTC)
'''Sê-stûnî ("┆" = ne tab), li gorî Cureyê rêzimanî:'''
{| class="wikitable"
! Cureyê rêzimanî !! Stûna 2 !! Stûna 3 !! Têbînî
|-
| Navdêr / Serenav
| Cîns (hilbijêre ▾: mê / nêr / ... — tenê heke ziman zayendê rêzimanî hebe)
| '''Jimar''' (?) — pirs li jêr
| Qadên "Forma mê/nêr/pirjimar/yekjimar" niha jî li vir hene — ma ev di "Jimar"-ê de tên tevlihev kirin, an cuda dimînin?
|-
| Rengdêr
| (heman m/n/p/y forman ku Navdêr distîne)
| Sûperlatîv (sûp) / Komparatîv (komp)
| Ne li ser her du nîgaran hatiye behs kirin — ma sê-stûnî li vir jî tê bikaranîn?
|-
| Lêker
| '''Cure''' = Cureyê lêkerê (hilbijêre ▾: gerguhêz / negerguhêz / xwerû-... / pêkhatî-... / hevedudanî-... / biwêjî-... — 10 vebijêrk)
| Di heman stûnê de: Rehê dema niha / Rehê dema borî / Rehê dema bilanî (her sê qadên nivîsandinê ne, ne hilbijêrk in)
| Ma "Cure" her sê rehan di xwe de digire wek yek stûn, an her yek stûneke cuda ye?
|-
| Tîp (herf/sembol)
| Girdêk (gir) / Hûrek (hûr)
| —
| Ne behs kiriye
|-
| Her cureyek, li gorî zimên
| Transliterasyon (tr) / Transkrîpsiyon (ts)
| —
| Bi zimên ve girêdayî ye, ne bi cureyê rêzimanî ve
|-
| Her cureyek, tenê ziman=japonî
| Hanja / Translit. hanja
| —
| Ne bi cureyê rêzimanî ve girêdayî ye
|-
| Her cureyek
| '''Tercîhên pêşketî''' (kolapsî): form, formaalt, nekatcure, nekatcins, nekatçendterm, serlînkneke, nekatpalîndrom, neformtr
| —
| Vebijarkî, ji bo hemû cureyan
|}
'''Tabên jêrîn — Mane ‖ Bilêvkirin ‖ Beşên din ("||" = tab):'''
{| class="wikitable"
! Mane !! Bilêvkirin !! Beşên din (hemû 18 hêman)
|-
| mane, nimûne, wêne
| şablonên bilêvkirinê (mînak: IPA, kîte)
| tewandin, etîmolojî, bi zaravayên din, werger, jê, têkildar, herwiha binêre, formên alternatîv, hevmane, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, bnr-h
|}
'''Pirsên vekirî:'''
# "Jimar" (yekjimar/pirjimar) — qadeke ku nîşan dide têketî bi xwe yekjimar e an pirjimar e. Ev cuda ye ji "Forma yekjimar (y)"/"Forma pirjimar (p)" ya heyî (ku forma DÎN a kelîmeyê tomar dike). Herdu bimînin, an werin yek kirin?
# Ma sê-stûnî tenê ji bo Navdêr/Lêker e, an ji bo Rengdêr/Tîp jî heman mantiq derbas dibe?
# Di "Beşên din" de: dixwazî pêşî navnîşek/hilbijartinek nîşan bide (17 hêmanên din jî tê de), û qad tenê piştî hilbijartinê xuya bibin?
:Tu dikarî start.js ji Tercîh/ Amûran rakî û bi test.js li Amûran herî taliyê ye biceribînî. Claude ya li jor hazir kir, Jimar ez dikarim îzah bikim. Feqat bi min di tab-a maneyê de lazim e Bi zaravayên din (heta wek werger her car bila li rûpelan were zêdekirin) û werger jî hebe.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:18, 14 tebax 2026 (UTC)
:;Navdêr: Tenê "cîns" û "jimar" bes e. "Forma mê", "Forma nêr", ... nayê fêm kirin. Bi min wekî "Tercîhên pêşketî" bi navkî din veşartî çêke.
:;Lêker: "Cureyê lêkerê" nayê fêm kirin, bi min pêşta "geguhez/negerguhêz" were hilbijartin, paşê jî form "hevdudanî/pêkatî/..." ji ber ku her kes vana nizanî... Dîsa jî tu dizanî.
:Bi min beşên "Hevmane" û dijmane jî bike de nav beşa Mane ("tab") ji ber ku pirr tên xwestin.
:Şûnda Wêne jî Nimûne/jêgirtin...
:Herwiha hemû bi rengê şîn hatine çêkirin, nizanim beşên din (===) bi rengê gewr an reş çito dibe? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:35, 14 tebax 2026 (UTC)
::Nimûne/jêgirtin, hevmane, Dijmane li binê her maneyê hene. ji ber ku wana bi maneyê ve girêdayî ne lazim e wisa bimîne feqat belkî şiklekî baştir bibînin. Hînbûna bikaranîna formeke nû bi qasî hînbûna koda wîkiyê zor e heqîqeten... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 14 tebax 2026 (UTC)
:::Min şaş fêm meke feqet te amûrek ji bo Wikihez çêkiriye :) Tu dixwazî ku hemû tiştên ku te çêkiriye tevlî vê Gadgetê bikî û bikarhênerên din jî bi kar bînin... Tu jî dizanî ku kesên ku tên vira çito peyv çêdikin: Ziman > Mane > Mînak > Hevmane > Dijmane ! Du Gadget çêke yek ji bo bikarhênerên ku hertim li vira ne, yekeke din jî ji bo bikarhênerên din (yên ku pirr nên vira).
:::Herwiha kesên ku hertim li vira ne bi Gadgetê gotar çênekin, ''copy/paste'' dikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 14 tebax 2026 (UTC)
::::Min xwest her tişt li ser rêza xwe be ji bo ku hîn bibin çi diçe kû derê feqat bi vî şiklî jî zêde dirêj û tevlihev dibe. Loma ez ê wekî tu dibêjî bikim her tiştê din bila collapsed pêşketî be. Ê ku dixwaze pê lê ke û binêre çi dikare were zêdekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 14 tebax 2026 (UTC)
:::::Niha baştir tê fêm kirin {{=)}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:01, 14 tebax 2026 (UTC)
::::::Min ji Opus 5 û Design Tool îstifade kiriye hema bêje milyonek token çûn :D pêşî dîzayn kir dûre dest pê kir wekî dîna bişuxile. Pereyê xwe %100 heq dike welleh :D feqat moda tarî ji bîr kiriye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:12, 14 tebax 2026 (UTC)
:::::::Spas! Etîmolojî jî wekî "Bilêvkirin", di beşekê din de cem wê, were çêkirin (Mane|Etîmolojî|Bilêvkirin|Beşên din)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:20, 14 tebax 2026 (UTC)
::::::::(Mane>Tewandin>Werger|Etîmolojî>Dûnde>|Bilêvkirin(formatek çawa?)>Resim|Eleqedar>Jê>Têkildar>Herwiha binêre|Beşên din)? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:29, 14 tebax 2026 (UTC)
:::::::::Nizanim, biceribîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:43, 14 tebax 2026 (UTC)
::::::::::"Li şablonekê bigere" ser Maneyê hatiye nivîsandin, nizanim rind tê fêm kirin an na? Min paşê fêm kir :)) Naskirina Wîkîferhengê hewce ye... Li bin were nivîsandin û "notek" hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:02, 15 tebax 2026 (UTC)
:::::::::::"Malûmatên pêşketî nîşan bide" jî bike ser "Hevman" ji ber ku pirr li jor e. Tu nizanî çi dike (nayê dîtin)... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:09, 15 tebax 2026 (UTC)
::::::::::::Dever/Devok jî were nivîsandin ? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 15 tebax 2026 (UTC)
:::::::::::::Tistekî din, dema ku tu maneyê dinivîsî bi otomatîk li jêr mane "2" tê nivîsandin, ew jî ne baş e (pirranî ne hewce ye). Bi min wekî "tab" cem "Mane 1" were vekirin (MAne1|Mane 2|Mane 3), bi otomatîk û an jî bi tikandina ser "Maneyeke din lê zêde bike" (cem beşa Maneyê). An jî bin beşa Wergerê bi nivîsa "maneyeke din lê zêde bike" were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:44, 15 tebax 2026 (UTC)
::::::::::::::Ji bo mane2, mane3, ... problemek heye: "Beşên din" ji bo kîjan maneyê ye :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:49, 15 tebax 2026 (UTC)
:::::::::::::::Devok/dever ya rastî ji bo kurmancî herî muhîm e lê diçe qismê etîketê ew jî veşartî ye, Niha çawa çêbûye? Min butona +mane zêde bike îlawe kiriye. Beşên din ji bo hemû maneyan e, ji bo maneyên xisûsî lazim e yek qismê malûmatên zêdetir bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:01, 15 tebax 2026 (UTC)
::::::::::::::::mane1, man2, ... cem beşa "Mane" çito ye, wekî:
::::::::::::::::<poem>
:::::::::::::::: '''Mane''' || Etîmoljî || Eleqedar || Beşên din
:::::::::::::::: -----------------------------------------------------------------
:::::::::::::::: Mane 1 || '''Mane 2''' || + Mane zêde bike [[Wêne:OOjs UI icon trash-destructive.svg|18px]]
:::::::::::::::: <small>Maneya kelîmeyê binivîse. Mane otomatîk tê ...</small>
::::::::::::::::┌───────────────────────────────────┐
::::::::::::::::│ Kelîmeyên ji bo... │
::::::::::::::::└───────────────────────────────────┘
:::::::::::::::: -----------------------------------------------------------------
::::::::::::::::</poem>
::::::::::::::::Belkî çêkirin zor e... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:59, 15 tebax 2026 (UTC)
:::::::::::::::::Ev nimûne çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:05, 15 tebax 2026 (UTC)
::::::::::::::::::Te beşa "Bilêvkirin" kiriye cem "Beş1", erê. Li gor vê lojîkê "Etîmlojî" jî were cem beşa "Bilêvkirin", na? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:19, 15 tebax 2026 (UTC)
::::::::::::::::::Etîmolojî h4 e Bilêvkirin h3 loma min Bilêvkirin xiste cem "Beş1". Tu dibêjî îzaha qismê Maneyê "Maneya kelîmeyê binivîse. Mane otomatîk tê" li bin tabên Maneyê bin ne wisa? Pir ferq dike? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:23, 15 tebax 2026 (UTC)
:::::::::::::::::::Checkbox ji bo Etîmolojiya "Beş2" zêde bikim? Eger truthy be bila Etîmolojî h3 be û here pêşiya crz (cureyê rêzimanî em jî hinek kurtkirin bi kar bînin êdî) ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 15 tebax 2026 (UTC)
:::::::::::::::::::Na min tiştekî witira negot, min tenê ji bo fêmkirinê ew şemayê çêkir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:27, 15 tebax 2026 (UTC)
::::::::::::::::::::OK, niha rind bû :) "Maneyeke din zêde bike" çito ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 15 tebax 2026 (UTC)
::::::::::::::::::::Temam wê demê her tişt çêbû... Însan hîn dikare gelek tişt zêde bike, feqat usage limits hene :D Min session-a 5 saeatî temam kir. Dûre ez ê wisa bikim wextê pê li taba Mane-Etîmolojî dikî here jor, bi vî aawayî piştî dagirtina her tiştî li jêr însan dikare bi hêsanî maneyên din binivîse. Pişhingî jî ez ê paqiya zimanî bikim, piştî hinek ceribandinan em nû bikin. Eger fikrên nû were dûre dîsa dikarin îlawe bikin :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:35, 15 tebax 2026 (UTC)
:::::::::::::::::::::Temam! Dema ku tu "Mane 2" an "Mane 3" bi şaşî nivîsand... çito tê/n jêbirin? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 15 tebax 2026 (UTC)
::::::::::::::::::::::Vala bikî bes e, beşên vala dernakevin li outputê. butonek çêkin? li kû derê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:49, 15 tebax 2026 (UTC)
::::::::::::::::::::::Cem "Maneyeke din zêde bike" '''jêbibe''' were nivîsandin an jî wêneyê [[Wêne:OOjs UI icon trash-destructive.svg|22px]]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:51, 15 tebax 2026 (UTC)
:::::::::::::::::::::::Mane 1 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] Mane 2 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] +Maneyeke din zêde bike çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 15 tebax 2026 (UTC)
::::::::::::::::::::::::Nizanim kîjan nîşan baş e: va jî heye: <code>✕</code> [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:03, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::Bi min tenê yek carê were nivîsandin ji ber ku tu bi şasî maneyên xwe jî jê dibirî (dema ku tu li ser maneyê ditikînî) :) Li cem "Beş zêde bike" jî were nivîsandin [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::Bi kurdmancî "Rehê dema bilanî (subj)" ne hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:48, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::::Lazim e pêşî em şablonan li gorî zimanan çêkin wekî en.wikt {{şx|en-lêker}} dûre li gorî parametreyên TemplateDatayê em qutiyan dikarin zêde an kêm bikin. Niha Malûmatên şablonên etîmolojî, bilêbkirin û guharto hhwd. ji templatedayê dîstinê wextê pê li butona (i) dikî, lê gelek ji wan ne xwedî TemplateData ne. Hîn karên din jî hene divê em bikin wekî sererastkirina şablonên jêgirtinê. Loma niha wisa bimîne. Em li gotûbêja amûrê beşa Daxwaz çêkin? tiştên ku tên bîra me em li wir binivîsin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::::Min yên ku te gotine çêkir feqat bi min resma jêbirinê baş nebû, tenê ya talî tê jêbirin ne tiştekî normal e? Min Beşên din jî xiste nav Eleqedar, Beşên din ne hewce ye. Bila zêde tab jî nebin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:11, 15 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}}
Îkona jêbirinê (min rind fêm nekir) li gor daxwaza xwe bike... Herwiha çend bug hene:
# Di nav maneyê de nivîs "zû" nayê nivîsandin.
# Dema ku tu beşê dikî "Navdêr", paşê jî "Lêker": wekî "Navdêr" dimîne. Tu ê li ser "Bilêvkirin" bitikînî.
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:18, 15 tebax 2026 (UTC)
:Min 1 fêm kir, ji ber ku Werger û Bi zaravayên din tên updatekirin dereng dimîne, belkî li şûna wextê nivîsînê, em bikin piştî nivîsînê ew ê zûtir be.
:Ya 2an min nikaribû bibînim çawa ev problem derdikeve, li ku derê wekî Navdêr dimîne piştî guhartinê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:40, 15 tebax 2026 (UTC)
::Min fêm kir lazim e li Bilêvkirinê pê lê bikî da ku biguhêre... Ez ê rast bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 15 tebax 2026 (UTC)
:::Tiştekî din :) Formatên "Bilêvkirin" (kare) û "Beş1" (nîv-oval) ne wekhev in. Herwiha formatên "+ Beş zêde bike" û "+ Mane zêde bike" jî ne wekhev in. Tercihên te ne an jî bug e? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:37, 15 tebax 2026 (UTC)
::::Ev nîv-oval ji min re gelek ecêb tê. Yên din lazim in yek şekil bin wekî +Mane zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:40, 15 tebax 2026 (UTC)
:::::Li şûna "Ev ne pênase ye — şablonek bi kar bîne" ez ê çi binivîsim? Ti tişt nayê bîra min ya ku dikare vê bikaranînê îzah bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:43, 15 tebax 2026 (UTC)
::::::Maneyê bi şablonan çêke (rastnivîs, şas nivîs, aweyekî din, ku-Latn, ...) :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 15 tebax 2026 (UTC)
:::::::Bi min "rehê niha" û "rehê borî" were dîtin ji ber ku bê wan ew lêker nayê bikaranîn. Tiştekî pirr hewce ye... Te "Cureyê lêkerê:" nivîsandiye paşe jî bin wan "Rehên lêkerê (Lêker çito tê tewandin?):". Herwiha Beşa "Tewandin" hertim şablona "ku-tewîn-nav" dide.
:::::::(?) Dever/Dovok jî bin Manyê were nivîsandin: Ew pey li ku derê an di kîjan devokê de tê bikaranîn: ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:45, 16 tebax 2026 (UTC)
::::::::An jî di bin beşa "Tewandin"ê de vana pirs bike....r [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:56, 16 tebax 2026 (UTC)
::::::::Herkes nikare "rehê niha" û "rehê borî" bizane, eger em "dema niha" û "dema borî" bixwazin hêsantir tê fêmkirin û bi zimanên din jî tê bikaranîn. Feqat [[Modul:rêziman]] wana destek dide? Nayê bîra min... Ez ê dîzayna Dever/devok ji design toolê pirs bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:57, 16 tebax 2026 (UTC)
:::::::::Erê, bi parametreyên <code>niha=</code>/<code>nihatr=</code> û <code>borî=</code>/<code>borîtr=</code>. <code>subj=</code>Bnr, {{q|fa|داشتن}}, {{q|ku|emilandin}}, {{q|ku|emilîn}}. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:02, 16 tebax 2026 (UTC)
::::::::::Wana dizanim, ew reh in, ez dibêjim forma dema niha, forma dema borî, em fniha, fbila û fborî çêkin? Bi tirkî, îngilîzî jî dikare were bikaranîn... dikim, bikim, kir. yapiyorum, yap, yapti, doing, do, did? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:06, 16 tebax 2026 (UTC)
:::::::::::Ahh, min niha fêm kir :) Paşê jî li gor wan em dikarin rehê lêrê çêkin... Na ew nehatine çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:17, 16 tebax 2026 (UTC)
:::::::::::min ceriband [https://ku.wiktionary.org/w/index.php?title=yapmak&diff=prev&oldid=6334114] werga çitonî ye? Ez dibêjim zêdetir kes dikare forma dema niha bizane û binivîse feqat herkes nikare reh fêm bike çi ye û çawa ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:19, 16 tebax 2026 (UTC)
::::::::::::Bi min qutiya nivîsa Maneyê wekî yê "Nimûne" mezin were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:50, 16 tebax 2026 (UTC)
:::::::::::::Prompt-ên me pir zeîf in, lazim e pêşî jê re Promptek bidin çêkirin dûre herin qismê Codeê û promptê bizelîqînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:32, 16 tebax 2026 (UTC)
::::::::::::::Êdî AI promptên ne baş jî fêm dikin û serrerast dikin, pêşta hukirina formata promptan hewce bû... Pirr pirs hatine pirs kirin; niha, ew dizanin kîjan rind e û tu çi dixwazî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:45, 16 tebax 2026 (UTC)
:::::::::::::::Format niha baş e feqet ew Gadjet ji bo kesên ku debî-ya înternetê yên wan hêdî ye rind dişuxile? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:58, 16 tebax 2026 (UTC)
::::::::::::::::(ji bo lêkeran) Bi min, bin "Lêker derbasdar e?" were nivîsandin wekî "Tewandina lêkerê". Pêş qutiyan jî "ez" an "min" (borî) ( li gor curyê lêkerê were nivîsandin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:04, 16 tebax 2026 (UTC)
::::::::::::::::In this sentence, debî means "(data) throughput" or "bandwidth" — internet connection speed. It's a loanword from French débit (as in débit internet), which is used in Kurdish tech/web contexts for how fast a connection is.
::::::::::::::::So the phrase "kesên ku debî-yên înternetê yên wan hêdî ye" translates to "people whose internet speed/bandwidth is slow" — i.e., people on slow connections.
::::::::::::::::The full sentence is Ghybu asking: "The format is good now, but does this Gadget run well for people with slow internet connections?" — essentially a performance/loading-speed concern, not a formatting one.
::::::::::::::::If you want, I can look at what might be slowing the gadget down for low-bandwidth users (e.g., how many separate resources it loads, any heavy assets) and suggest something before you reply to them.
::::::::::::::::yes take a look
::::::::::::::::Read 6 files, ran 2 commands, searched 2 patterns
::::::::::::::::I dug into what actually gets downloaded on a fresh load of the wizard. Breakdown of the four files:
::::::::::::::::File Size (raw / gzip)
::::::::::::::::start.js 270 KB / 77 KB
::::::::::::::::data.json 114 KB / 36 KB
::::::::::::::::styles.css 87 KB / 19 KB
::::::::::::::::util.js 16 KB / 6 KB
::::::::::::::::So roughly 490 KB of source, ~140 KB after gzip — that's the real number Ghybu is feeling on a slow connection, especially the first time they load the gadget after any update (MediaWiki caches it aggressively afterward via a versioned URL, so it's mainly a one-time-per-version cost, not every page load).
::::::::::::::::Two concrete findings:
::::::::::::::::A real, fixable bottleneck: the whole gadget's initialization is gated behind mw.loader.using(["oojs-ui-core", "oojs-ui.styles.icons-moderation"], ...) — meaning the wizard doesn't start rendering anything until both modules arrive. But icons-moderation is only there for the trash-can icon on the delete buttons, and those buttons don't even exist until someone has 2+ Mane or 2+ Beş sections. Right now every user pays that extra module round-trip before seeing the form at all, even though most people won't touch a delete button in the first few seconds. Deferring that one module load until the first trash button is actually built would let the form render sooner on a slow connection, for free.
::::::::::::::::Not really fixable without bigger surgery: data.json's "ziman" (language list) is 75 KB of its 114 KB — the single biggest chunk in the whole payload. But that's needed for the very first field in the wizard (the language picker), so lazy-loading it would just delay the first thing the person needs, not help. The only way to shrink this meaningfully would be turning the language field into a live-search that fetches matches instead of shipping the whole list upfront — a much bigger change, and probably not worth it unless load time is a serious complaint from multiple people.
::::::::::::::::Want me to make the icons-moderation fix? It's small and low-risk — I'd move that module load to fire lazily right before the first delete button is created instead of blocking the initial render. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 16 tebax 2026 (UTC)
:::::::::::::::::Erê :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:14, 16 tebax 2026 (UTC)
::::::::::::::::::-:) Em çend sed ziman hilbijêrin tenê wana nîşan bidin? Niha tenê yên ku lêmayên wan hene tên lîstekirin. nêzîkî 3k ziman
::::::::::::::::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:21, 16 tebax 2026 (UTC)
:::::::::::::::::::Bi min Jimar di tercîhên pêşketî de veşartî be. Zêde nayê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
::::::::::::::::::::"Tewandina lêkerê" rast çêbû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
:::::::::::::::::::::Erê jimar were veşartin. Tewandina lêkerê rind bûye feqet dema bilanî ji bo zazakî ye.
:::::::::::::::::::::Qutiya "Cureyê rêzimanî" pirr nêzîkî nivîsa "Lêker derbasdar e?" ye û dûr nivîsa jor e.
:::::::::::::::::::::Herwiha Gadjetê "Bi otomatîk tijî bike" pirr rind e feqet min bikaranîna wî zû fêm nekir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:25, 16 tebax 2026 (UTC)
::::::::::::::::::::::Min "Çend caran wekî dema bê nayê çêkirin... " fêm nekir. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:34, 16 tebax 2026 (UTC)
:::::::::::::::::::::::"Maneyê bi şablonan çêke (rastnivîs, şasnivîs, aweyekî din)" bi min lazim e pêşiya qutiya mane be, ji ber ku me devok, etîket kategorî zêde kir niha ecêb bûye [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
:::::::::::::::::::::::Bi formên standardê dema fermanî wekî forma sêyem (yekjimar) a dema bê ye (dema bê: ew ê/dê bibêje// fermanî: bibêje). Feqet di nav devokan de ne hertim wisa ye. Mînak bi devoka rojhilatî: ew dê bêt (dema bê) û bibê (dema fermanî). Li vira dema fermanî bi dema bê nayê çêkirin. Feqet ne hewce ye ji ber ku li jêr devok tê xwastin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
::::::::::::::::::::::::An jî dema bê were xwastin, dema fermanî wekî yê standardê tê çêkirin. Min tevlî hev kir :)) . Bi min bes e Gadjetekî tewandinê nîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:49, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::Em bilanî bikin "fermanî" zêde serê xwe neêşînin, wekî din em ê deh qutiyên din çêkin :D Piştî hinek clean-upê jî em biweşînin ji bo niha bes e {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:55, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::Problem, bi rastî, forma bê ye :)) Di çend devokan de pêşgira "bi-" nayê bikaranîn (torî, badînî, ... [[Modul:ku-tewandin#L-298]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::Te tabloya tewandina reşwî dît? [[Bikarhêner:Kurdanparez/sandbox]]. Şanseke me heye ku ew nekirine standard :). Feqet pirr entresan e, formên mê û nêr jî hene. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:23, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::::Qet behsa standardiyê meîne (meyîne?) :D Ji wî alî de gelek xemgîn im. Min ji bo kurmancî forma dema bê tevlî kir, fermanî ji bo zimanên din. Feqat Claude niha naxebite dibêje "Claude is temporarily unavailable" {{=D}} Niha divê çi were paqijkirin. Tiştekî kêm ma?
::::::::::::::::::::::::::::"All the code changes (jimar dropdown from data.json for every language, fdbe → fdbê rename with fdbêtr added, and the "Taybetmendî" heading removed) are done and verified on my end — nothing is lost" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::::Min placeholdera URL, DOI... guhart niha baştir tê fêmkirin înşela? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:07, 16 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::::}}Tiştên ku lazim in were guhartin:
* Placeholdera qismê "Tewandina "kelîme" binivîse"
[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:11, 16 tebax 2026 (UTC)
:Divê ns 108 destek bide. Wextê ns 108 be berîya beşa zimanî {{şx|jinûvesazkirî|langcode}} were zêdekirin, wekî din her tişt eynî ne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 16 tebax 2026 (UTC)
::"Ev mane li ku derê an..." bike "Bi vê maneyê li kû derê an..." [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 16 tebax 2026 (UTC)
:::Ji bo forma bê kesê sêyem were xwastin (ew ê/dê ...) ji ber ku di forma standardê de wekî forma fermanî ye: (tu) ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:22, 16 tebax 2026 (UTC)
:::Kitêb/kovar/websîte lazim e formata fireh kurt wekî [[MediaWiki:Gadget-wikt.add-examples.js]] bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:23, 16 tebax 2026 (UTC)
::::Butonên lînk û înfo ya dropdowna Etîmolojî û Bilêvkirin naxebitin. Ya dropdowna şablonên Maneyê jî du heb hene, yek di nav dropdownê de ku naxebitin ya din piştî hilbijartinê xuya dibin ku ew jî ne hewce ne, tenê lazim e di nav qutiyê de be. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:30, 16 tebax 2026 (UTC)
:::::Di "Tercîhên min" de butona "Hemû cache paqij bike" lazim e cache-yên etîket, dever û kategoriyê jî paqij bike. Niha nake. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:31, 16 tebax 2026 (UTC)
::::::Pill-ên Hevmane/Dijmane etc. lazim in di derveyî qutiya kelîmeyê de xuya bibin piştî nivîsandinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:33, 16 tebax 2026 (UTC)
:::::::"- beşek hilbijêre -" bike "- binebeşên din hilbijêre -" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:34, 16 tebax 2026 (UTC)
::::::::Çavkanî section dropdown linebreak dixe berîya "* şablon" lazim e linebreak piştî navê şablonê were. Îkonên lînk û înfo li vir jî naxebitin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 16 tebax 2026 (UTC)
:::::::::"Binêre herwiha (bnr-h)" bike "Herwiha binêre (h-bnr)"
:::::::::"Şablona bnr-h li qismê herî jor a rûpelê" bike "Şablona h-bnr li qismê herî jor a rûpelê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:36, 16 tebax 2026 (UTC)
::::::::::@[[Bikarhêner:Ghybu|Ghybu]] Bi min problem nema. Fikrê te? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:18, 17 tebax 2026 (UTC)
== [[Mediawiki:Gadget-QQ.js]] ==
Ev amûr di şikle Minerva de. Her daîm vê xeletiyê dide:
<syntaxhighlight lang="js">
TypeError: Cannot read properties of null (reading 'addEventListener') at css () at runScript () at Array.<anonymous> () at flushCssBuffer ()
</syntaxhighlight> [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:29, 18 tîrmeh 2026 (UTC)
:* [[Bikarhêner:Wikihez/skrîpt/js/Gadget-QQ.js]] --> [[Mediawiki:Gadget-QQ.js]]
:* [[Bikarhêner:Wikihez/Gadget-QQ.css]] --> [[Mediawiki:Gadget-QQ.css]]
:Ez dibêjim çêbû lê ka çend roj biceribînim. Tiştên ku hatin sererastkirin:
:# Google Books API Key dikarî zêde bikî, dixe cookies û di gerokê de dimîne. Berê bê API Key dixebitiya lê êdî mecbûr e ku API Key-a te hebe, bidestxistina wê ne zêde zor e lê dîsa jî tevlihev e.
:# Êdî di mobîlê de dixebite, heta di mobîlê de xweştir xuya dike.
:# Moda tarî
:# OOUI dewsa jquery.ui
:# Li qutiyê çi dixwazî zêde bikî, dikare li gorî îsm, sifet, fiîl bitewîne. Lê ji bo fiîl tenê dikare çend heb inflection bîne.
:# Butona paqij bike lê zêde kir.
:# Çend paqijiyên jêgirtinên Google Kitêbê niha hene.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:38, 18 tîrmeh 2026 (UTC)
::Tewandina fiîlan û xeletiya modulê lazim e were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:40, 21 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-wikt.add-examples.js]] ==
Min ev amûr hinek rojane kir feqat çend problem hene berî ku bar bikin.
Tiştên ku min guhart:
# Êdî li sayfeyên bi {{ş|maneid}} jî dixebite,
# Cihê di wîkîtekstê de yê ku jêgirtin were îlawekirin baştir bû,
# [[Mediawiki:Gadget-citoid-helper.js|citoid helper]] zêde kir ji bo ku bi hêsanî çavkanî bi riya doi, isbn an urlyê werin tijîkirin.
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.css]] --> [[MediaWiki:Gadget-wikt.add-examples.css]]
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.js]] --> [[MediaWiki:Gadget-wikt.add-examples.js]]
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 19 tîrmeh 2026 (UTC)
:Min "wikt." qismên cache kirinê ya wîkiya fransî bi [[MediaWiki:Gadget-StorageUtils.js]] guhartiye. loma ji bîr nekin wê amûrê li [[MediaWiki:Gadgets-definition]] zêde bikin. û navê amûrê jî biguhêrînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:15, 23 tîrmeh 2026 (UTC)
::Berê li ser WF:ceribandin dixebitî. niha naxebite. Divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:10, 7 tebax 2026 (UTC)
== Gadget-translation editor ==
* [[MediaWiki:Gadget-translation editor.js]]: divê formata en.wîktê were bikaranîn ji ber ku me gelek tercime ji en.wîktê kopî kir û ev js hîn nikare formata wekî li jêr bi kar bîne.
<syntaxhighlight lang="wikitext">
* {{Z|sh}}:
*: Kirîlî:
*: Latînî
</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Mediawiki:Gadget-linkify.js]] ==
* ''rûpeleke'' wekî<syntaxhighlight lang="wikitext">[[rûpeleke]]</syntaxhighlight> lînk dike, bi regexê belkî dikare wisa bike <syntaxhighlight lang="wikitext">[[rûpel]]eke</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
* Amûreke din wekî [[User:Wikihez/Gadget-linkify.js|linkify]] lazim e ku herfên mezin biçûk bike ji bo kurmancî.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:{{r|Bikarhêner}} Merheba bira, dikarî viya jî bar bikî?:
* [[Special:PermanentLink/6291974|linkify]] --> [[Mediawiki:Gadget-linkify.js]]
Spas--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:42, 29 tîrmeh 2026 (UTC)
:Dûre [https://ku.wiktionary.org/wiki/MediaWiki:Gadget-start.js#L-283 li vir] wiktviLinkify bike kuLinkify xêra xwe [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:51, 29 tîrmeh 2026 (UTC)
::Min ji bîr kir lê zêde bikim niha nikare "Dİ" bike "di", xeletiyeke berbelav a di nivîsên kurdî de, herfên din ên wekî ll yan rr jî hewce ne werin zêdekirin. Dûre em zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:42, 29 tîrmeh 2026 (UTC)
:::Min nû ferq kir, sayfeyên ku hîn nehatine çêkirin wîkîlînk nake. Problemeke mezin e divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:15, 30 tîrmeh 2026 (UTC)
::::Na, lînk dike, tiştekî din heye... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:49, 31 tîrmeh 2026 (UTC)
== Amûra manayê ==
Amûreke din ji bo nivîsina maneyê wekî Nimûne zêde bike lazim e [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6276876 js] [https://ku.wiktionary.org/w/index.php?title=%C5%9Eablon:ceribandin2&oldid=6265629 şablon] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6265642 css] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:12, 23 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-jeder.js]] ==
@[[Bikarhêner:Ghybu|Ghybu]] Çavkaniyên jêderê di moda pêşdîtinê de jêderê vedişêre, ew jî min gelek eciz dike wextê çavkanî zêde dikim. Min ji Claude pirsî û kodan nîşanî wê da, wisa cewab da:
Yes — and this file actually explains the whole thing precisely, better than my earlier guess about extract-stripping.
What's happening: the CSS rule
<syntaxhighlight lang="css">
.jeder{
display:none;
}
</syntaxhighlight>
hides every citation by default, *unconditionally*. The only thing that ever makes it visible again is the gadget's JS: it finds each `.jeder`, builds the short "`— (year, author, +)`" summary from the `.j-pewist` fields, and either reveals the short version or — for citations with no `.j-pewist` fields — explicitly sets `display:inline` on the original.
So visibility of citations depends entirely on this JS running. In the normal article view (Image 1) it runs, so you see the collapsed "`(1918, E. Rehmî Ji Mala Hekariyan, Amadekar: M. E. Bozarslan, +)`" form. In whatever generated Image 2 — a hover-preview/extract/API render — gadget JS doesn't execute, so nothing ever removes the `display:none`, and the citation vanishes completely rather than degrading to "still visible, just not collapsed."
<b>The fix:</b> flip the default so the *un-collapsed* state is the safe fallback, and let JS do the hiding only after it successfully builds the short version. That way, any context without JS shows the full citation instead of nothing.
CSS — remove the blanket hide:
<syntaxhighlight lang="css">
.jeder {
/* intentionally no display:none here — see Gadget-... .js */
}
</syntaxhighlight>
JS — do the hiding from script, after the short form exists:
{{veşêre-ser}}
<syntaxhighlight lang="js">
// Ji bo şablonên [[Şablon:jêder]] hatiye nivîsandin.
// Parametreyên ku di navbera "<span class="j-pewist">...</span>" an "{{j-pêwîst|...}}" de ne venaşêre.
$(function() {
function nivisaKurtTevliBike(sund, nivisKurt) {
var zede = $('<a>+</a>')
.addClass('click')
.attr('href', '#')
.attr('title', 'zêdetir')
.css('font-weight', 'bold');
var kurt = $(' <a>↩</a>')
.addClass('click-kurt')
.attr('href', '#')
.attr('title', 'veşêre')
.css('font-weight', 'bold');
$('<span>' + nivisKurt + '</span>')
.append(zede).append(')')
.addClass('jeder-kurt')
.css('font-size', '0.85em')
.insertAfter(sund);
sund.append(' ').append(kurt);
// Only hide the full citation once the short summary actually exists.
// If this script never runs (no-JS clients, hover-preview extracts,
// action=render, etc.), the full citation stays visible instead of
// silently disappearing.
sund.css('display', 'none');
}
function vesere() {
$('.jeder').each(function() {
var elmTab = $(this).find('.j-pewist').clone();
if (elmTab.length) {
var elm = '';
for (var i = elmTab.length - 1; i >= 0; i--) {
elm = elmTab.eq(i).html() + ', ' + elm;
}
var nivisKurt = ' — (' + elm;
nivisaKurtTevliBike($(this), nivisKurt);
}
// else: no j-pewist fields to shorten — leave the full citation
// as it already renders (visible), nothing to do.
});
}
vesere();
$(".click").click(function(e) {
e.preventDefault();
$(this).parent('.jeder-kurt').css('display', 'none');
$(this).parent('.jeder-kurt').prev().css('display', 'inline');
});
$(".click-kurt").click(function(e) {
e.preventDefault();
$(this).parent('.jeder').css('display', 'none');
$(this).parent('.jeder').next().css('display', 'inline');
});
});
</syntaxhighlight>
Net effect:
- JS-enabled readers (normal article view): identical experience to today — collapsed short form with `+`/`↩` toggle.
- No-JS contexts (previews, extracts, screen readers before JS loads, print, etc.): full citation is shown instead of nothing — a real regression fix rather than a cosmetic one.
One caveat worth being upfront about: this fixes it *if* the preview context renders the gadget's HTML/CSS but doesn't execute gadget JS (which is the far more common case, and what this code strongly suggests is happening). If Image 2 instead comes from a pipeline that strips the `.jeder`/`.jegirtin-text` elements out of the HTML entirely before this CSS/JS even gets a chance to apply, no on-wiki CSS/JS change can help — that'd need the extract-generation config changed instead. Given what this gadget file shows, though, I'd bet on the CSS-hide explanation.
{{veşêre-bin}}
Em dûre biceribînin eger xeletî tinebe, vî tiştî jî rast bikin. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:00, 23 tîrmeh 2026 (UTC)
:Ez APIya MediaWiki rind nizanim belkî Claude dizane:) Em nikarin bibêjin dema ku moda "''action=edit''" e fonksiyona <code>function vesere()</code> bi kar meyîne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:57, 24 tîrmeh 2026 (UTC)
::Na, di moda "edît"-ê de <code>var elmTab = null</code>... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:13, 24 tîrmeh 2026 (UTC)
?:
<syntaxhighlight lang="js">
if (
mw.config.get('wgAction') === 'edit' ||
mw.config.get('wgAction') === 'submit'
){
var elmTab = [];
} else {
var elmTab = $(this).find('.j-pewist').clone();
}
</syntaxhighlight>
--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:31, 24 tîrmeh 2026 (UTC)
:Ez jî nizanim Claude çiqas ji mediawiki fêm dike lê heta niha ti tişt nîne ku min jê pirs kir û nikaribû çêke, bila wekî reklam nebe feqat Claude mikemmel e, dibêjim "li vir çima xeletiyeke wisa derdikeve" di dosyeya 2000 rêzî de diçe tiştê xelet tenê rast dike tiştekî din naguherîne. Chatgpt û aistudio qet wisa nakin, eynî dosyeyê deh cara dinivîsin, deh netîceyên cuda derdikevin :D
:Em dikarin vê amûrê wextê edit û submitê neaktîv bikin wekî <syntaxhighlight lang="js">if (!(action === "edit" || action === "submit")) {
:return;</syntaxhighlight> lê wê demê display:none wê çibe nizanim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:23, 24 tîrmeh 2026 (UTC)
::display:none ne hewce ye ma jixwe di nav kodê [https://ku.wiktionary.org/w/index.php?title=MediaWiki:Gadget-jeder.js&oldid=6069025#L-49 L-49] de inline/none heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:53, 24 tîrmeh 2026 (UTC)
:Min bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.js&diff=prev&oldid=6290816#L-28] ceriband feqat nexebitî. "display:none" di pêşdîtinê de dixebite feqat js naşixule. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:43, 25 tîrmeh 2026 (UTC)
::Kodên JSê pir hez nakim (pêşta hukurina APIya MWyê hewce bû) dîsa jî ez ê seh bikim :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:49, 27 tîrmeh 2026 (UTC)
:::Ez niha bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6290820] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6290821#L-8] bi kar tînim ti problem nîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:09, 27 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-PagePreviews.js]] ==
Ez dibêjim em vê amûrê jî çêkin [[:en:MediaWiki:Gadget-PagePreviews.js]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:56, 3 tebax 2026 (UTC)
= Moda tarî =
== [[Şablon:wîkîpediya]] ==
Rengê tarî de xirab e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:36, 4 tebax 2026 (UTC)
= Bot =
Karên ku bi botê werin çêkirin ji kerema xwe li vir binivîsin.
== Kategorî:Biwêj ==
[[:Kategorî:Biwêj]] divê were paqijkirin, lîsteya madeyên {{ş|biwêj}} çêke, yên ku tê de bûn, kirin, man hene filtre bike yên din bi destî tê çêkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Jêgirtin ==
Xistina jêgirtinên beşa [https://ku.wiktionary.org/w/index.php?search=insource%3A%2F%3D+*Ji+%5BWw%5D%C3%AAjey%C3%AA+*%3D%2F&title=Taybet%3AL%C3%AAger%C3%AEn&profile=advanced&fulltext=1&ns0=1&searchToken=8rh0bmw8dccsi7aynl6zli8uf ji wêjeyê] li beşa maneyê. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Bota Toolforgeê ==
Botekî Toolforgeê lazim e ji bo ku xeletiyên biçûk wekî şablonên bi navên îngilîzî werin kurdîkirin yan jî valahiya beşan rast bike û hwd.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:Min [[Bikarhêner:Wikihez/skrîpt/py/kuwiktkozmetik.py|paqijiyen kozmetîk]] hazir kir feqat hîn navên şablonan naguhêre. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 4 tebax 2026 (UTC)
== Updatew ==
Bota [[Bikarhêner:Wikihez/skrîpt/py/updateW.py|updateW]] divê were rojanekirin. Yan jî yekî nû were çêkirin. Şablonên {{ş|W}} di beşa tercimeyan de sererast dike û zimanan rêz dike. Ya niha xeletiya navê zimanî dide.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== sihir --> sêhr ==
Tercimeyên ''[[sêhr]]'' hemû dibêje ''sihir'' loma bi botê lazim e kirin ''sêhr''. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:43, 18 tîrmeh 2026 (UTC)
:Sihirbaz --> sêhrbaz [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 18 tîrmeh 2026 (UTC)
== Tercimeyên dîroknas ==
Li tercimeyên [[dîroknas]], hevmaneyên wê hewce ye were îlawekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:44, 18 tîrmeh 2026 (UTC)
== Mewlûd ==
Sererastkirina jêgirtinên mewlûdê [https://ku.wiktionary.org/w/index.php?search=insource%3A%2Fw%5C%3AMela+His%C3%AAn%C3%AA+Batey%C3%AE%2F&title=Taybet%3AL%C3%AAger%C3%AEn&ns0=1&searchToken=8m0tguy3sqt7edr4sg0u8hedo] wekî [https://ku.wiktionary.org/w/index.php?title=dax%C3%AEdar&curid=284303&diff=6265851&oldid=6142285] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:43, 20 tîrmeh 2026 (UTC)
== [[Şablon:jêder]] ==
Şablon jêder axlebî bi şiklê MM/YYYY sal tê de heye, bi botê em dikarin wana bixin {{para|sal}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:07, 17 tebax 2026 (UTC)
pxnifu3vm25ftsdmrfjimssvn6keih3
6334372
6334364
2026-08-17T17:46:48Z
Ghybu
1393
/* MediaWiki:Gadget-start.js */ Bersiv
6334372
wikitext
text/x-wiki
{{/ser}}
= Karên ferhengî =
== Mane ==
* [[:Kategorî:Madeyên bê beşa mane]]
** [[:Kategorî:Madeyên bê beşa mane bi kurmancî]]
Li ser van rûpelan ev made hene ku maneyan wan nehatine nivîsin û divê werin nivîsin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Etîmolojî ==
*[[:Kategorî:Daxwazên etîmolojiyê]]
**[[:Kategorî:Daxwazên etîmolojiyê bi kurmancî]]
Li ser van rûpelan ev made hene ku etîmolojiya wan hîn nehatine nivîsin. Ji bo alîkariyê binêre [[WF:Etîmolojî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Cureyê rêzimanî ==
* [[:Kategorî:Gotarên bê kategoriya rêzimanî]]
* [[:Kategorî:Gotarên bê kategoriya rêzimanî bi kurmancî]]
Li ser van rûpelan ev made hene ku cureyên wan yên rêzimanî hîn nehatine nivîsin. Ji bo alîkariyê binêre [[Alîkarî:Cureyên peyvan]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Kontrol ==
* [[:Kategorî:Kontrol]]
** [[:Kategorî:Kontrola maneyê]]
Ev madde bi botê hatine çêkirin, yek bi yek divê werin kontrolkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
= Şablon =
== IPA ==
* {{ş|IPA}} divê [[Modul:IPA]] bi kar bîne. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Deng ==
Kurdîkirina belgekirina {{ş|deng}} lazim e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 19 tîrmeh 2026 (UTC)
= Modul =
== Modul:form of/templates ==
* [[Modul:form of/templates]]: Divê ev modul were rojanekirin û xeletiyên berê wekî ([[hevalê]]) werin rastkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:Merheba @[[Bikarhêner:Ghybu|Ghybu]] Te gotibû di vê modulê de xeletî hene, jkx tu dikarî li vir binivîsî çi kêmasî hene, daku wextê rojane dikim ez ji bîr nekim? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:16, 21 tîrmeh 2026 (UTC)
*::Silav. Nizanim ji bîra min çûye... Herwiha ji bo çêkirina formên navdêran zanînên cîns û tîpa dawî bes e:
*::* (-ê, mê) => çemandî, bangkirin
*::* (-ê, nêr) => îzafe
*::* (-ê, mê û nêr) => îzafe (nêr), bangkirin (mê), çemandî (mê)
*::* (-a) => îzafe (mê)
*::* (-ekî) => îzafe û çemandî (nêr, nebinavkirî)
*::* (-eke) = > îzafe (mê, nebinavkirî)
*::* ...
*:: <code>if find(mw.title.getCurrentTitle().subpageText, 'ekî$') then ... end</code>
*::<code><nowiki>{{ku-tewîn-navForm|<navdêr>|<zayend>}</nowiki></code>
*::Tablo û nivîs di yekcarî de tên çêkirin...
*::Feqet problem lêzêdekirina mînakan e. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:34, 21 tîrmeh 2026 (UTC)
*:::Lêzêdekirina kîjan nimûneyan? Ev koda js ji bo çi ye? Qisûrê nenêre min fêm nekir :/ [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:13, 21 tîrmeh 2026 (UTC)
*::::Li vira ([[hevalê#Formeke navdêrê]]) di bin maneyên 1. û 2. mînak nayên tevlî kirin :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 21 tîrmeh 2026 (UTC)
*:::::Ev tercîh e, ne mecbûrî ye, em dikarin [https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&diff=prev&oldid=6276350 wisa] bikin. Wekî din? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:29, 21 tîrmeh 2026 (UTC)
*::::::Min li jor tênê got ku tenê parametreyên "navdêr" û "zayend" hewce nee. Bi '''[//ku.wiktionary.org/w/index.php?title=heval%C3%AA&curid=969734&diff=6276353&oldid=6276350 vî]''' awayî jî tê çêkirin. Parametreyên din ne hewce ne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:38, 21 tîrmeh 2026 (UTC)
*:::::::Ji bo wê şabloneke din lazim e, bila ev şablon wekî îngilîzî bimîne em ji bo kurmancî yekî din çêkin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:42, 21 tîrmeh 2026 (UTC)
*::::::::[https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&oldid=6276885 wisa] çawa ye? Bi min nivîsîna nimûneyan zêde ne lazim e ji ber ku ti kes serê xwe pê naêşîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:46, 23 tîrmeh 2026 (UTC)
*:::::::::@[[Bikarhêner:Ghybu|Ghybu]] Vê şablonê bi kar bînin an na? Navê şablonê kê çi be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:07, 24 tîrmeh 2026 (UTC)
== Modul:columns ==
* [[Modul:columns]] were rojanekirin û {{ş|kol3}} bila lînka ''nîşan bide'' deyne beriya sernavê, kolê di qutiya rengî de bila nebe. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:etymon ==
* [[Modul:etymon kevn]]: Rojanekirin hewce ye. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Modul:ziman/code to canonical name]] ==
Belgekirina skrîptê û js divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:31, 22 tîrmeh 2026 (UTC)
:* [[MediaWiki:UpdateLanguageNameAndCode.js]]
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:50, 22 tîrmeh 2026 (UTC)
::* [[Modul:ziman/print]]
::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:52, 22 tîrmeh 2026 (UTC)
:::*[[Modul:ziman/şablon]]
:::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:56, 22 tîrmeh 2026 (UTC)
::::* [[MediaWiki:Gadget-translation editor.js/ziman.json]]
::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:06, 22 tîrmeh 2026 (UTC)
== [[Modul:quote]] ==
Piştî ev modul hate hazirkirin, lazim e navên jegirtinan werin rastkirin û li hemûyan {{param|jêgirtin}} were zêdekirin. Bnr. [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Mem û Zîn]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 5 tebax 2026 (UTC)
::Ev modul hema bêje hazir e. Hûn li ser van madeyan çi difikirin:
# {{para|tarîx}} lazim e full date be eger na, lazim e {{para|sal}} were bikaranîn. Feqat "'''2021''' kanûna pêşîn 23" bi vî şiklî bi min ne problem e çnkî derdê en.wikt "attestation" e delîla herî pêşî jî sal e.
#: {{demo|sep=wisa xuya dibe<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|tarîx=23 December 2021|nivîskar=Harry Hobbs; George Williams|sernav=Micronations and the Search for Sovereignty|url=https://www.cambridge.org/core/books/micronations-and-the-search-for-sovereignty/B0414A3A322695D9EE71E4CDDFDA06DE|weşanxane=Cambridge University Press|rûpel=155|isbn=978-1-009-15012-5|jêgirtin=jêgirtin.|t=-}}</nowiki>}}
#{{para|amadekar}}/{{para|edîtor}}/{{para|terciman}} bi şiklê edîtoriya, amadekariya, tercimekirina
#: {{demo|sep=dide<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|sernav=Medical Surgical Nursing|serî=Juta's Manual of Nursing|cildê-seriyê=volume 4|edîtor=Sophie Mataniele Mogotlane; Joyce Mokoena; Motshedisi Eunice Chauke; Anne Young|sal=2005|weşanxane=Juta Academic|isbn=978-0702171840|rûpel=!12-29|jêgirtin=jêgirtin|t=-}}</nowiki>}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:09, 10 tebax 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Hîn gelek tişt hene divê îzah bikin û fikrê we pirs bikim, feqat îzahkirin pir zor e, em dest bi bikaranînê bikin û dûre tiştên ku hûn naxwazin em dikarin biguherînîn, dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:11, 10 tebax 2026 (UTC)
::Dibe, piştî çêbû, em di praktîkê de bibînin ku çawa ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:05, 10 tebax 2026 (UTC)
= Javascript =
== [[Mediawiki:Gadget-citoid-helper.js]] ==
Ji bo bi citoidê kopîkirina malûmatên çavkaniyan amûreke wisa lazim e. Xisûsen ji ber ku bi çend amûrên din dikare were bikaranîn.
Feqat çend problem hene, şablonên me de kêmasiya parametreyan gelek in û xerîteyên citoidê hîn nehatine çêkirin. Wana sererast bikin dûre vê amûrê çêkin, piştre [[MediaWiki:Gadget-start.js|start.js]] û [[MediaWiki:Gadget-wikt.add-examples.js|add examples.js]] were rojanekirin.
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:04, 19 tîrmeh 2026 (UTC)
:Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]], ez dibêjim karê Modul:quote zêde wext bistîne, bila em dest bi bikaranîna van amûran bikin dûre kêmasiyan em ê temam bikin înşela. Dikarî vana li cihên xwe yên rast kopî paste bikî?
# [[Special:PermanentLink/6291574|Bikarhêner:Wikihez/skrîpt/js/Gadget-citoid-helper.js]] --> [[Mediawiki:Gadget-citoid-helper.js]]
# Ya li jêr kopî bike qismê herî dawî ya AmûrênVeşartî li [[MediaWiki:Gadgets-definition]]:
## <code><nowiki>* citoid-helper[ResourceLoader|hidden|default|dependencies=mediawiki.util]|citoid-helper.js</nowiki></code>
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:58, 26 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-start.js]] ==
Rojanekirin hewce ye. Min gelek tişt lê zêde kir lê ez niha test dikime. Piştre em bar bikin:
* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 17 tîrmeh 2026 (UTC)
:Min [[MediaWiki:Gadget-start/data.json|data.json]] çêkir ji bo ku bi hêsanî malûmatên heyî werin guhartin bêyî guhartina Javascriptê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:49, 18 tîrmeh 2026 (UTC)
:Xeletiyên ku ji bîr nekim rast bikim:
:* Wextê min çavkanî "kitêb" hilbijart û bi destî tije kir xeletiyeke [https://ku.wiktionary.org/w/index.php?title=xezete&diff=prev&oldid=6265392 wisa] derket her kelîme hate lînkkirin.
:* <code>html("⠿")</code> ev ne lazim e.
:* textarea ji bo çavkaniyên jêgirtinan xaseten yên sernav hîn jî ne draggable e.
:* pêşdîtina zindî checkbox wextê tê rakirin, pêşdîtin winda nabe.
:* Butona mobîlê ya vekirina formê di moda tarî de belî nabe, tenê wekî qutiyeke tarî xuya dibe.
:* Metna hişyarkirinê di guhartina rûpelên heyî de lazim e baştir bikim.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:51, 18 tîrmeh 2026 (UTC)
::Ev problem çêbûn, ez ê binêrim ka çi problemên din hene. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:15, 19 tîrmeh 2026 (UTC)
:::Çêke: Ji bo qismê çavkaniyê yek dikare API call û Kategorî:Çavkanî bi kar bîne ji bo ku şablon zêde bikî ji yên heyî. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 19 tîrmeh 2026 (UTC)
::::Ev jî çêbû, min beşa çavkanî jî çêkir. Eger xeletî dernekeve û problema valahiya piştî etîketê ji bîr nekim, piştî rojanekirina amûrên linkify, û citoid em dikarin bar bikin. Ez ê ti tiştê nû îlawe nekim, zêde dirêj bû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:14, 21 tîrmeh 2026 (UTC)
:::::Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]]. Dikarî wana bar bikî?
:::::* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
:::::* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
:::::* [[MediaWiki:Gadgets-definition]] wisa rojane bike:
:::::* start[ResourceLoader|default|package|dependencies=mediawiki.util,ext.gadget.StorageUtils,ext.gadget.citoid-helper]|start.js|start.css|start.js/data.json
:::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:39, 5 tebax 2026 (UTC)
{{şirove}} Dewama ''[[Special:PermanentLink/6333688|Gotûbêja bikarhêner:Rewiye tine#WF:Nû]]'' bila gotûbêja wî em spam nekin. :D
{{r|Ghybu}} dikarî [[Special:PermanentLink/6333689|vê htmlê]] binêrî claude rast fêm kiriye yan na? Daxîne û wek html qeyd bike. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:43, 14 tebax 2026 (UTC)
:Min seh wîkîyên din jî kir, li wîkiya fransî tiştekî wekî daxwaza min çêkirine (tiştên ku ne hewce ne wekî "tab" çêkirine an jî hatine vêşartin): [//fr.wiktionary.org/w/index.php?title=fdsdfdsf&action=edit&redlink=1]. Formata me yê berî jî bi vê lojîkê hatibû çêkirin, wekî de.wiktionary ([//de.wiktionary.org/w/index.php?title=sdfdsfsdfs&action=edit&preload=Vorlage:Formatvorlage_(Substantiv_vereinfacht)]) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:01, 14 tebax 2026 (UTC)
::PS: Dîsa jî Claude min rind fêm kiriye :)) Feqet min got ku beşên Mane", "Bilêvkirin" û "Beşên din" cem hev (wekî "tab") ". Û dema ku tu terî di nav "Besên din" Hevmane, Jê, ... li wira werin dayîn. Wêne/Resim û Nimûne jî di nav beşa ("tab") "Mane" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:17, 14 tebax 2026 (UTC)
::Erê ya fr.wikt min jî dîtibû jixwe fikrê tab min ji wan girtibû :D (feqat min li cem wan tenê tab-a maneyê dît yên din nedît çawa tê vekirin nizanim) Berê hemû beş piştî maneyê bibûn, dûre min sê Tab çêkir, Bingehîn+Lînkkirin+Bikaranîn. Niha lazim e Tabeke din çêkim. Ya ku claude çêkiriye bnr. [[Special:PermanentLink/6333698#L-7]] ne baş e bnr. [[Special:PermanentLink/6333703|qiyaskirinê li ser vê htmlê]] Êdî ez Claude abuse dikime ji ber ku Pro ye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:26, 14 tebax 2026 (UTC)
:::Claude dibêje:
:::Good clarification — that's a real simplification, not just a relabel. Updated STEP_GROUPS from 5 tabs down to 3:
:::Mane — the definitions themselves, plus Wêne (image), now grouped in with it instead of sitting in a separate misc tab. Nimûne needed no change since it was already rendering inside each Mane sense block, not as its own step.
:::Bilêvkirin — unchanged, still just pronunciation.
:::Beşên din — everything else in one flat bucket: hevmane, jê, têkildar, herwiha binêre, formên alternatîv, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, tewandin, etîmolojî, zaravayên din, werger, and bnr-h — instead of being split across the old Bingehîn/Lînkkirin/Bikaranîn/Din tabs.
:::No DOM-order changes were needed this time — the fix from the blank-tab bug looks up the tab bar's position dynamically now, so it didn't matter that I regrouped which steps go where. Same two files, saved to your Wiki folder — worth another pass through all three tabs once you've pasted this in, same as last time. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:34, 14 tebax 2026 (UTC)
::::Mînak:
Ziman
---------------------------------------------
Cureyê rêzimanî (Navdêr)
Navdêr (hilbijêre) ┆ Cîns (hilbijêre) ┆ Jimar (hilbijêre)
- lêker -mê -yekjimar
- rengdêr -nêr -pirjimar
- ... -... -...
---------------------------------------------
Mane || Bilêvkirin || Beşên din
hilbijêre:
- mane - IPA - Hevmane
- nimûne - kîte - Dijmane
- wêne
Dema ku tu "Cureyê rêzimanî" dikî "Lêker" :
Cureyê rêzimanî (Lêker)
Lêker (hilbijêre) ┆ Cure (hilbijêre)
- navdêr -rehê niha
- rengdêr -rehê borî
- ... -...
---------------------------------------------
┆ : select/option ("tab" nîne, tê dîtin)<br>
|| : "tab"
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:45, 14 tebax 2026 (UTC)
'''Sê-stûnî ("┆" = ne tab), li gorî Cureyê rêzimanî:'''
{| class="wikitable"
! Cureyê rêzimanî !! Stûna 2 !! Stûna 3 !! Têbînî
|-
| Navdêr / Serenav
| Cîns (hilbijêre ▾: mê / nêr / ... — tenê heke ziman zayendê rêzimanî hebe)
| '''Jimar''' (?) — pirs li jêr
| Qadên "Forma mê/nêr/pirjimar/yekjimar" niha jî li vir hene — ma ev di "Jimar"-ê de tên tevlihev kirin, an cuda dimînin?
|-
| Rengdêr
| (heman m/n/p/y forman ku Navdêr distîne)
| Sûperlatîv (sûp) / Komparatîv (komp)
| Ne li ser her du nîgaran hatiye behs kirin — ma sê-stûnî li vir jî tê bikaranîn?
|-
| Lêker
| '''Cure''' = Cureyê lêkerê (hilbijêre ▾: gerguhêz / negerguhêz / xwerû-... / pêkhatî-... / hevedudanî-... / biwêjî-... — 10 vebijêrk)
| Di heman stûnê de: Rehê dema niha / Rehê dema borî / Rehê dema bilanî (her sê qadên nivîsandinê ne, ne hilbijêrk in)
| Ma "Cure" her sê rehan di xwe de digire wek yek stûn, an her yek stûneke cuda ye?
|-
| Tîp (herf/sembol)
| Girdêk (gir) / Hûrek (hûr)
| —
| Ne behs kiriye
|-
| Her cureyek, li gorî zimên
| Transliterasyon (tr) / Transkrîpsiyon (ts)
| —
| Bi zimên ve girêdayî ye, ne bi cureyê rêzimanî ve
|-
| Her cureyek, tenê ziman=japonî
| Hanja / Translit. hanja
| —
| Ne bi cureyê rêzimanî ve girêdayî ye
|-
| Her cureyek
| '''Tercîhên pêşketî''' (kolapsî): form, formaalt, nekatcure, nekatcins, nekatçendterm, serlînkneke, nekatpalîndrom, neformtr
| —
| Vebijarkî, ji bo hemû cureyan
|}
'''Tabên jêrîn — Mane ‖ Bilêvkirin ‖ Beşên din ("||" = tab):'''
{| class="wikitable"
! Mane !! Bilêvkirin !! Beşên din (hemû 18 hêman)
|-
| mane, nimûne, wêne
| şablonên bilêvkirinê (mînak: IPA, kîte)
| tewandin, etîmolojî, bi zaravayên din, werger, jê, têkildar, herwiha binêre, formên alternatîv, hevmane, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, bnr-h
|}
'''Pirsên vekirî:'''
# "Jimar" (yekjimar/pirjimar) — qadeke ku nîşan dide têketî bi xwe yekjimar e an pirjimar e. Ev cuda ye ji "Forma yekjimar (y)"/"Forma pirjimar (p)" ya heyî (ku forma DÎN a kelîmeyê tomar dike). Herdu bimînin, an werin yek kirin?
# Ma sê-stûnî tenê ji bo Navdêr/Lêker e, an ji bo Rengdêr/Tîp jî heman mantiq derbas dibe?
# Di "Beşên din" de: dixwazî pêşî navnîşek/hilbijartinek nîşan bide (17 hêmanên din jî tê de), û qad tenê piştî hilbijartinê xuya bibin?
:Tu dikarî start.js ji Tercîh/ Amûran rakî û bi test.js li Amûran herî taliyê ye biceribînî. Claude ya li jor hazir kir, Jimar ez dikarim îzah bikim. Feqat bi min di tab-a maneyê de lazim e Bi zaravayên din (heta wek werger her car bila li rûpelan were zêdekirin) û werger jî hebe.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:18, 14 tebax 2026 (UTC)
:;Navdêr: Tenê "cîns" û "jimar" bes e. "Forma mê", "Forma nêr", ... nayê fêm kirin. Bi min wekî "Tercîhên pêşketî" bi navkî din veşartî çêke.
:;Lêker: "Cureyê lêkerê" nayê fêm kirin, bi min pêşta "geguhez/negerguhêz" were hilbijartin, paşê jî form "hevdudanî/pêkatî/..." ji ber ku her kes vana nizanî... Dîsa jî tu dizanî.
:Bi min beşên "Hevmane" û dijmane jî bike de nav beşa Mane ("tab") ji ber ku pirr tên xwestin.
:Şûnda Wêne jî Nimûne/jêgirtin...
:Herwiha hemû bi rengê şîn hatine çêkirin, nizanim beşên din (===) bi rengê gewr an reş çito dibe? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:35, 14 tebax 2026 (UTC)
::Nimûne/jêgirtin, hevmane, Dijmane li binê her maneyê hene. ji ber ku wana bi maneyê ve girêdayî ne lazim e wisa bimîne feqat belkî şiklekî baştir bibînin. Hînbûna bikaranîna formeke nû bi qasî hînbûna koda wîkiyê zor e heqîqeten... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 14 tebax 2026 (UTC)
:::Min şaş fêm meke feqet te amûrek ji bo Wikihez çêkiriye :) Tu dixwazî ku hemû tiştên ku te çêkiriye tevlî vê Gadgetê bikî û bikarhênerên din jî bi kar bînin... Tu jî dizanî ku kesên ku tên vira çito peyv çêdikin: Ziman > Mane > Mînak > Hevmane > Dijmane ! Du Gadget çêke yek ji bo bikarhênerên ku hertim li vira ne, yekeke din jî ji bo bikarhênerên din (yên ku pirr nên vira).
:::Herwiha kesên ku hertim li vira ne bi Gadgetê gotar çênekin, ''copy/paste'' dikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 14 tebax 2026 (UTC)
::::Min xwest her tişt li ser rêza xwe be ji bo ku hîn bibin çi diçe kû derê feqat bi vî şiklî jî zêde dirêj û tevlihev dibe. Loma ez ê wekî tu dibêjî bikim her tiştê din bila collapsed pêşketî be. Ê ku dixwaze pê lê ke û binêre çi dikare were zêdekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 14 tebax 2026 (UTC)
:::::Niha baştir tê fêm kirin {{=)}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:01, 14 tebax 2026 (UTC)
::::::Min ji Opus 5 û Design Tool îstifade kiriye hema bêje milyonek token çûn :D pêşî dîzayn kir dûre dest pê kir wekî dîna bişuxile. Pereyê xwe %100 heq dike welleh :D feqat moda tarî ji bîr kiriye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:12, 14 tebax 2026 (UTC)
:::::::Spas! Etîmolojî jî wekî "Bilêvkirin", di beşekê din de cem wê, were çêkirin (Mane|Etîmolojî|Bilêvkirin|Beşên din)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:20, 14 tebax 2026 (UTC)
::::::::(Mane>Tewandin>Werger|Etîmolojî>Dûnde>|Bilêvkirin(formatek çawa?)>Resim|Eleqedar>Jê>Têkildar>Herwiha binêre|Beşên din)? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:29, 14 tebax 2026 (UTC)
:::::::::Nizanim, biceribîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:43, 14 tebax 2026 (UTC)
::::::::::"Li şablonekê bigere" ser Maneyê hatiye nivîsandin, nizanim rind tê fêm kirin an na? Min paşê fêm kir :)) Naskirina Wîkîferhengê hewce ye... Li bin were nivîsandin û "notek" hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:02, 15 tebax 2026 (UTC)
:::::::::::"Malûmatên pêşketî nîşan bide" jî bike ser "Hevman" ji ber ku pirr li jor e. Tu nizanî çi dike (nayê dîtin)... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:09, 15 tebax 2026 (UTC)
::::::::::::Dever/Devok jî were nivîsandin ? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 15 tebax 2026 (UTC)
:::::::::::::Tistekî din, dema ku tu maneyê dinivîsî bi otomatîk li jêr mane "2" tê nivîsandin, ew jî ne baş e (pirranî ne hewce ye). Bi min wekî "tab" cem "Mane 1" were vekirin (MAne1|Mane 2|Mane 3), bi otomatîk û an jî bi tikandina ser "Maneyeke din lê zêde bike" (cem beşa Maneyê). An jî bin beşa Wergerê bi nivîsa "maneyeke din lê zêde bike" were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:44, 15 tebax 2026 (UTC)
::::::::::::::Ji bo mane2, mane3, ... problemek heye: "Beşên din" ji bo kîjan maneyê ye :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:49, 15 tebax 2026 (UTC)
:::::::::::::::Devok/dever ya rastî ji bo kurmancî herî muhîm e lê diçe qismê etîketê ew jî veşartî ye, Niha çawa çêbûye? Min butona +mane zêde bike îlawe kiriye. Beşên din ji bo hemû maneyan e, ji bo maneyên xisûsî lazim e yek qismê malûmatên zêdetir bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:01, 15 tebax 2026 (UTC)
::::::::::::::::mane1, man2, ... cem beşa "Mane" çito ye, wekî:
::::::::::::::::<poem>
:::::::::::::::: '''Mane''' || Etîmoljî || Eleqedar || Beşên din
:::::::::::::::: -----------------------------------------------------------------
:::::::::::::::: Mane 1 || '''Mane 2''' || + Mane zêde bike [[Wêne:OOjs UI icon trash-destructive.svg|18px]]
:::::::::::::::: <small>Maneya kelîmeyê binivîse. Mane otomatîk tê ...</small>
::::::::::::::::┌───────────────────────────────────┐
::::::::::::::::│ Kelîmeyên ji bo... │
::::::::::::::::└───────────────────────────────────┘
:::::::::::::::: -----------------------------------------------------------------
::::::::::::::::</poem>
::::::::::::::::Belkî çêkirin zor e... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:59, 15 tebax 2026 (UTC)
:::::::::::::::::Ev nimûne çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:05, 15 tebax 2026 (UTC)
::::::::::::::::::Te beşa "Bilêvkirin" kiriye cem "Beş1", erê. Li gor vê lojîkê "Etîmlojî" jî were cem beşa "Bilêvkirin", na? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:19, 15 tebax 2026 (UTC)
::::::::::::::::::Etîmolojî h4 e Bilêvkirin h3 loma min Bilêvkirin xiste cem "Beş1". Tu dibêjî îzaha qismê Maneyê "Maneya kelîmeyê binivîse. Mane otomatîk tê" li bin tabên Maneyê bin ne wisa? Pir ferq dike? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:23, 15 tebax 2026 (UTC)
:::::::::::::::::::Checkbox ji bo Etîmolojiya "Beş2" zêde bikim? Eger truthy be bila Etîmolojî h3 be û here pêşiya crz (cureyê rêzimanî em jî hinek kurtkirin bi kar bînin êdî) ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 15 tebax 2026 (UTC)
:::::::::::::::::::Na min tiştekî witira negot, min tenê ji bo fêmkirinê ew şemayê çêkir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:27, 15 tebax 2026 (UTC)
::::::::::::::::::::OK, niha rind bû :) "Maneyeke din zêde bike" çito ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 15 tebax 2026 (UTC)
::::::::::::::::::::Temam wê demê her tişt çêbû... Însan hîn dikare gelek tişt zêde bike, feqat usage limits hene :D Min session-a 5 saeatî temam kir. Dûre ez ê wisa bikim wextê pê li taba Mane-Etîmolojî dikî here jor, bi vî aawayî piştî dagirtina her tiştî li jêr însan dikare bi hêsanî maneyên din binivîse. Pişhingî jî ez ê paqiya zimanî bikim, piştî hinek ceribandinan em nû bikin. Eger fikrên nû were dûre dîsa dikarin îlawe bikin :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:35, 15 tebax 2026 (UTC)
:::::::::::::::::::::Temam! Dema ku tu "Mane 2" an "Mane 3" bi şaşî nivîsand... çito tê/n jêbirin? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 15 tebax 2026 (UTC)
::::::::::::::::::::::Vala bikî bes e, beşên vala dernakevin li outputê. butonek çêkin? li kû derê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:49, 15 tebax 2026 (UTC)
::::::::::::::::::::::Cem "Maneyeke din zêde bike" '''jêbibe''' were nivîsandin an jî wêneyê [[Wêne:OOjs UI icon trash-destructive.svg|22px]]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:51, 15 tebax 2026 (UTC)
:::::::::::::::::::::::Mane 1 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] Mane 2 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] +Maneyeke din zêde bike çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 15 tebax 2026 (UTC)
::::::::::::::::::::::::Nizanim kîjan nîşan baş e: va jî heye: <code>✕</code> [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:03, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::Bi min tenê yek carê were nivîsandin ji ber ku tu bi şasî maneyên xwe jî jê dibirî (dema ku tu li ser maneyê ditikînî) :) Li cem "Beş zêde bike" jî were nivîsandin [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::Bi kurdmancî "Rehê dema bilanî (subj)" ne hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:48, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::::Lazim e pêşî em şablonan li gorî zimanan çêkin wekî en.wikt {{şx|en-lêker}} dûre li gorî parametreyên TemplateDatayê em qutiyan dikarin zêde an kêm bikin. Niha Malûmatên şablonên etîmolojî, bilêbkirin û guharto hhwd. ji templatedayê dîstinê wextê pê li butona (i) dikî, lê gelek ji wan ne xwedî TemplateData ne. Hîn karên din jî hene divê em bikin wekî sererastkirina şablonên jêgirtinê. Loma niha wisa bimîne. Em li gotûbêja amûrê beşa Daxwaz çêkin? tiştên ku tên bîra me em li wir binivîsin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::::Min yên ku te gotine çêkir feqat bi min resma jêbirinê baş nebû, tenê ya talî tê jêbirin ne tiştekî normal e? Min Beşên din jî xiste nav Eleqedar, Beşên din ne hewce ye. Bila zêde tab jî nebin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:11, 15 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}}
Îkona jêbirinê (min rind fêm nekir) li gor daxwaza xwe bike... Herwiha çend bug hene:
# Di nav maneyê de nivîs "zû" nayê nivîsandin.
# Dema ku tu beşê dikî "Navdêr", paşê jî "Lêker": wekî "Navdêr" dimîne. Tu ê li ser "Bilêvkirin" bitikînî.
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:18, 15 tebax 2026 (UTC)
:Min 1 fêm kir, ji ber ku Werger û Bi zaravayên din tên updatekirin dereng dimîne, belkî li şûna wextê nivîsînê, em bikin piştî nivîsînê ew ê zûtir be.
:Ya 2an min nikaribû bibînim çawa ev problem derdikeve, li ku derê wekî Navdêr dimîne piştî guhartinê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:40, 15 tebax 2026 (UTC)
::Min fêm kir lazim e li Bilêvkirinê pê lê bikî da ku biguhêre... Ez ê rast bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 15 tebax 2026 (UTC)
:::Tiştekî din :) Formatên "Bilêvkirin" (kare) û "Beş1" (nîv-oval) ne wekhev in. Herwiha formatên "+ Beş zêde bike" û "+ Mane zêde bike" jî ne wekhev in. Tercihên te ne an jî bug e? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:37, 15 tebax 2026 (UTC)
::::Ev nîv-oval ji min re gelek ecêb tê. Yên din lazim in yek şekil bin wekî +Mane zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:40, 15 tebax 2026 (UTC)
:::::Li şûna "Ev ne pênase ye — şablonek bi kar bîne" ez ê çi binivîsim? Ti tişt nayê bîra min ya ku dikare vê bikaranînê îzah bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:43, 15 tebax 2026 (UTC)
::::::Maneyê bi şablonan çêke (rastnivîs, şas nivîs, aweyekî din, ku-Latn, ...) :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 15 tebax 2026 (UTC)
:::::::Bi min "rehê niha" û "rehê borî" were dîtin ji ber ku bê wan ew lêker nayê bikaranîn. Tiştekî pirr hewce ye... Te "Cureyê lêkerê:" nivîsandiye paşe jî bin wan "Rehên lêkerê (Lêker çito tê tewandin?):". Herwiha Beşa "Tewandin" hertim şablona "ku-tewîn-nav" dide.
:::::::(?) Dever/Dovok jî bin Manyê were nivîsandin: Ew pey li ku derê an di kîjan devokê de tê bikaranîn: ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:45, 16 tebax 2026 (UTC)
::::::::An jî di bin beşa "Tewandin"ê de vana pirs bike....r [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:56, 16 tebax 2026 (UTC)
::::::::Herkes nikare "rehê niha" û "rehê borî" bizane, eger em "dema niha" û "dema borî" bixwazin hêsantir tê fêmkirin û bi zimanên din jî tê bikaranîn. Feqat [[Modul:rêziman]] wana destek dide? Nayê bîra min... Ez ê dîzayna Dever/devok ji design toolê pirs bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:57, 16 tebax 2026 (UTC)
:::::::::Erê, bi parametreyên <code>niha=</code>/<code>nihatr=</code> û <code>borî=</code>/<code>borîtr=</code>. <code>subj=</code>Bnr, {{q|fa|داشتن}}, {{q|ku|emilandin}}, {{q|ku|emilîn}}. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:02, 16 tebax 2026 (UTC)
::::::::::Wana dizanim, ew reh in, ez dibêjim forma dema niha, forma dema borî, em fniha, fbila û fborî çêkin? Bi tirkî, îngilîzî jî dikare were bikaranîn... dikim, bikim, kir. yapiyorum, yap, yapti, doing, do, did? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:06, 16 tebax 2026 (UTC)
:::::::::::Ahh, min niha fêm kir :) Paşê jî li gor wan em dikarin rehê lêrê çêkin... Na ew nehatine çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:17, 16 tebax 2026 (UTC)
:::::::::::min ceriband [https://ku.wiktionary.org/w/index.php?title=yapmak&diff=prev&oldid=6334114] werga çitonî ye? Ez dibêjim zêdetir kes dikare forma dema niha bizane û binivîse feqat herkes nikare reh fêm bike çi ye û çawa ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:19, 16 tebax 2026 (UTC)
::::::::::::Bi min qutiya nivîsa Maneyê wekî yê "Nimûne" mezin were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:50, 16 tebax 2026 (UTC)
:::::::::::::Prompt-ên me pir zeîf in, lazim e pêşî jê re Promptek bidin çêkirin dûre herin qismê Codeê û promptê bizelîqînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:32, 16 tebax 2026 (UTC)
::::::::::::::Êdî AI promptên ne baş jî fêm dikin û serrerast dikin, pêşta hukirina formata promptan hewce bû... Pirr pirs hatine pirs kirin; niha, ew dizanin kîjan rind e û tu çi dixwazî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:45, 16 tebax 2026 (UTC)
:::::::::::::::Format niha baş e feqet ew Gadjet ji bo kesên ku debî-ya înternetê yên wan hêdî ye rind dişuxile? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:58, 16 tebax 2026 (UTC)
::::::::::::::::(ji bo lêkeran) Bi min, bin "Lêker derbasdar e?" were nivîsandin wekî "Tewandina lêkerê". Pêş qutiyan jî "ez" an "min" (borî) ( li gor curyê lêkerê were nivîsandin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:04, 16 tebax 2026 (UTC)
::::::::::::::::In this sentence, debî means "(data) throughput" or "bandwidth" — internet connection speed. It's a loanword from French débit (as in débit internet), which is used in Kurdish tech/web contexts for how fast a connection is.
::::::::::::::::So the phrase "kesên ku debî-yên înternetê yên wan hêdî ye" translates to "people whose internet speed/bandwidth is slow" — i.e., people on slow connections.
::::::::::::::::The full sentence is Ghybu asking: "The format is good now, but does this Gadget run well for people with slow internet connections?" — essentially a performance/loading-speed concern, not a formatting one.
::::::::::::::::If you want, I can look at what might be slowing the gadget down for low-bandwidth users (e.g., how many separate resources it loads, any heavy assets) and suggest something before you reply to them.
::::::::::::::::yes take a look
::::::::::::::::Read 6 files, ran 2 commands, searched 2 patterns
::::::::::::::::I dug into what actually gets downloaded on a fresh load of the wizard. Breakdown of the four files:
::::::::::::::::File Size (raw / gzip)
::::::::::::::::start.js 270 KB / 77 KB
::::::::::::::::data.json 114 KB / 36 KB
::::::::::::::::styles.css 87 KB / 19 KB
::::::::::::::::util.js 16 KB / 6 KB
::::::::::::::::So roughly 490 KB of source, ~140 KB after gzip — that's the real number Ghybu is feeling on a slow connection, especially the first time they load the gadget after any update (MediaWiki caches it aggressively afterward via a versioned URL, so it's mainly a one-time-per-version cost, not every page load).
::::::::::::::::Two concrete findings:
::::::::::::::::A real, fixable bottleneck: the whole gadget's initialization is gated behind mw.loader.using(["oojs-ui-core", "oojs-ui.styles.icons-moderation"], ...) — meaning the wizard doesn't start rendering anything until both modules arrive. But icons-moderation is only there for the trash-can icon on the delete buttons, and those buttons don't even exist until someone has 2+ Mane or 2+ Beş sections. Right now every user pays that extra module round-trip before seeing the form at all, even though most people won't touch a delete button in the first few seconds. Deferring that one module load until the first trash button is actually built would let the form render sooner on a slow connection, for free.
::::::::::::::::Not really fixable without bigger surgery: data.json's "ziman" (language list) is 75 KB of its 114 KB — the single biggest chunk in the whole payload. But that's needed for the very first field in the wizard (the language picker), so lazy-loading it would just delay the first thing the person needs, not help. The only way to shrink this meaningfully would be turning the language field into a live-search that fetches matches instead of shipping the whole list upfront — a much bigger change, and probably not worth it unless load time is a serious complaint from multiple people.
::::::::::::::::Want me to make the icons-moderation fix? It's small and low-risk — I'd move that module load to fire lazily right before the first delete button is created instead of blocking the initial render. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 16 tebax 2026 (UTC)
:::::::::::::::::Erê :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:14, 16 tebax 2026 (UTC)
::::::::::::::::::-:) Em çend sed ziman hilbijêrin tenê wana nîşan bidin? Niha tenê yên ku lêmayên wan hene tên lîstekirin. nêzîkî 3k ziman
::::::::::::::::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:21, 16 tebax 2026 (UTC)
:::::::::::::::::::Bi min Jimar di tercîhên pêşketî de veşartî be. Zêde nayê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
::::::::::::::::::::"Tewandina lêkerê" rast çêbû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
:::::::::::::::::::::Erê jimar were veşartin. Tewandina lêkerê rind bûye feqet dema bilanî ji bo zazakî ye.
:::::::::::::::::::::Qutiya "Cureyê rêzimanî" pirr nêzîkî nivîsa "Lêker derbasdar e?" ye û dûr nivîsa jor e.
:::::::::::::::::::::Herwiha Gadjetê "Bi otomatîk tijî bike" pirr rind e feqet min bikaranîna wî zû fêm nekir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:25, 16 tebax 2026 (UTC)
::::::::::::::::::::::Min "Çend caran wekî dema bê nayê çêkirin... " fêm nekir. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:34, 16 tebax 2026 (UTC)
:::::::::::::::::::::::"Maneyê bi şablonan çêke (rastnivîs, şasnivîs, aweyekî din)" bi min lazim e pêşiya qutiya mane be, ji ber ku me devok, etîket kategorî zêde kir niha ecêb bûye [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
:::::::::::::::::::::::Bi formên standardê dema fermanî wekî forma sêyem (yekjimar) a dema bê ye (dema bê: ew ê/dê bibêje// fermanî: bibêje). Feqet di nav devokan de ne hertim wisa ye. Mînak bi devoka rojhilatî: ew dê bêt (dema bê) û bibê (dema fermanî). Li vira dema fermanî bi dema bê nayê çêkirin. Feqet ne hewce ye ji ber ku li jêr devok tê xwastin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
::::::::::::::::::::::::An jî dema bê were xwastin, dema fermanî wekî yê standardê tê çêkirin. Min tevlî hev kir :)) . Bi min bes e Gadjetekî tewandinê nîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:49, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::Em bilanî bikin "fermanî" zêde serê xwe neêşînin, wekî din em ê deh qutiyên din çêkin :D Piştî hinek clean-upê jî em biweşînin ji bo niha bes e {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:55, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::Problem, bi rastî, forma bê ye :)) Di çend devokan de pêşgira "bi-" nayê bikaranîn (torî, badînî, ... [[Modul:ku-tewandin#L-298]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::Te tabloya tewandina reşwî dît? [[Bikarhêner:Kurdanparez/sandbox]]. Şanseke me heye ku ew nekirine standard :). Feqet pirr entresan e, formên mê û nêr jî hene. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:23, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::::Qet behsa standardiyê meîne (meyîne?) :D Ji wî alî de gelek xemgîn im. Min ji bo kurmancî forma dema bê tevlî kir, fermanî ji bo zimanên din. Feqat Claude niha naxebite dibêje "Claude is temporarily unavailable" {{=D}} Niha divê çi were paqijkirin. Tiştekî kêm ma?
::::::::::::::::::::::::::::"All the code changes (jimar dropdown from data.json for every language, fdbe → fdbê rename with fdbêtr added, and the "Taybetmendî" heading removed) are done and verified on my end — nothing is lost" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::::Min placeholdera URL, DOI... guhart niha baştir tê fêmkirin înşela? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:07, 16 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::::}}Tiştên ku lazim in were guhartin:
* Placeholdera qismê "Tewandina "kelîme" binivîse"
[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:11, 16 tebax 2026 (UTC)
:Divê ns 108 destek bide. Wextê ns 108 be berîya beşa zimanî {{şx|jinûvesazkirî|langcode}} were zêdekirin, wekî din her tişt eynî ne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 16 tebax 2026 (UTC)
::"Ev mane li ku derê an..." bike "Bi vê maneyê li kû derê an..." [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 16 tebax 2026 (UTC)
:::Ji bo forma bê kesê sêyem were xwastin (ew ê/dê ...) ji ber ku di forma standardê de wekî forma fermanî ye: (tu) ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:22, 16 tebax 2026 (UTC)
:::Kitêb/kovar/websîte lazim e formata fireh kurt wekî [[MediaWiki:Gadget-wikt.add-examples.js]] bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:23, 16 tebax 2026 (UTC)
::::Butonên lînk û înfo ya dropdowna Etîmolojî û Bilêvkirin naxebitin. Ya dropdowna şablonên Maneyê jî du heb hene, yek di nav dropdownê de ku naxebitin ya din piştî hilbijartinê xuya dibin ku ew jî ne hewce ne, tenê lazim e di nav qutiyê de be. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:30, 16 tebax 2026 (UTC)
:::::Di "Tercîhên min" de butona "Hemû cache paqij bike" lazim e cache-yên etîket, dever û kategoriyê jî paqij bike. Niha nake. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:31, 16 tebax 2026 (UTC)
::::::Pill-ên Hevmane/Dijmane etc. lazim in di derveyî qutiya kelîmeyê de xuya bibin piştî nivîsandinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:33, 16 tebax 2026 (UTC)
:::::::"- beşek hilbijêre -" bike "- binebeşên din hilbijêre -" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:34, 16 tebax 2026 (UTC)
::::::::Çavkanî section dropdown linebreak dixe berîya "* şablon" lazim e linebreak piştî navê şablonê were. Îkonên lînk û înfo li vir jî naxebitin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 16 tebax 2026 (UTC)
:::::::::"Binêre herwiha (bnr-h)" bike "Herwiha binêre (h-bnr)"
:::::::::"Şablona bnr-h li qismê herî jor a rûpelê" bike "Şablona h-bnr li qismê herî jor a rûpelê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:36, 16 tebax 2026 (UTC)
::::::::::@[[Bikarhêner:Ghybu|Ghybu]] Bi min problem nema. Fikrê te? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:18, 17 tebax 2026 (UTC)
:::::::::::Temam. Çend tişt 1) Forma dema bê bin forma dema niha. 2) Beşa Tewandinê (ji bo lêkeran) vitira diminî an ji li gor danayên lêkerê (x-gh|reh, ...) tên tijî kirin. 3) Di qutiyên forma dema niha, borî, ... de zû nayê nivîsandin: înterneta te pirr rind e, tu ferq nakî? :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:46, 17 tebax 2026 (UTC)
== [[Mediawiki:Gadget-QQ.js]] ==
Ev amûr di şikle Minerva de. Her daîm vê xeletiyê dide:
<syntaxhighlight lang="js">
TypeError: Cannot read properties of null (reading 'addEventListener') at css () at runScript () at Array.<anonymous> () at flushCssBuffer ()
</syntaxhighlight> [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:29, 18 tîrmeh 2026 (UTC)
:* [[Bikarhêner:Wikihez/skrîpt/js/Gadget-QQ.js]] --> [[Mediawiki:Gadget-QQ.js]]
:* [[Bikarhêner:Wikihez/Gadget-QQ.css]] --> [[Mediawiki:Gadget-QQ.css]]
:Ez dibêjim çêbû lê ka çend roj biceribînim. Tiştên ku hatin sererastkirin:
:# Google Books API Key dikarî zêde bikî, dixe cookies û di gerokê de dimîne. Berê bê API Key dixebitiya lê êdî mecbûr e ku API Key-a te hebe, bidestxistina wê ne zêde zor e lê dîsa jî tevlihev e.
:# Êdî di mobîlê de dixebite, heta di mobîlê de xweştir xuya dike.
:# Moda tarî
:# OOUI dewsa jquery.ui
:# Li qutiyê çi dixwazî zêde bikî, dikare li gorî îsm, sifet, fiîl bitewîne. Lê ji bo fiîl tenê dikare çend heb inflection bîne.
:# Butona paqij bike lê zêde kir.
:# Çend paqijiyên jêgirtinên Google Kitêbê niha hene.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:38, 18 tîrmeh 2026 (UTC)
::Tewandina fiîlan û xeletiya modulê lazim e were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:40, 21 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-wikt.add-examples.js]] ==
Min ev amûr hinek rojane kir feqat çend problem hene berî ku bar bikin.
Tiştên ku min guhart:
# Êdî li sayfeyên bi {{ş|maneid}} jî dixebite,
# Cihê di wîkîtekstê de yê ku jêgirtin were îlawekirin baştir bû,
# [[Mediawiki:Gadget-citoid-helper.js|citoid helper]] zêde kir ji bo ku bi hêsanî çavkanî bi riya doi, isbn an urlyê werin tijîkirin.
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.css]] --> [[MediaWiki:Gadget-wikt.add-examples.css]]
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.js]] --> [[MediaWiki:Gadget-wikt.add-examples.js]]
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 19 tîrmeh 2026 (UTC)
:Min "wikt." qismên cache kirinê ya wîkiya fransî bi [[MediaWiki:Gadget-StorageUtils.js]] guhartiye. loma ji bîr nekin wê amûrê li [[MediaWiki:Gadgets-definition]] zêde bikin. û navê amûrê jî biguhêrînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:15, 23 tîrmeh 2026 (UTC)
::Berê li ser WF:ceribandin dixebitî. niha naxebite. Divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:10, 7 tebax 2026 (UTC)
== Gadget-translation editor ==
* [[MediaWiki:Gadget-translation editor.js]]: divê formata en.wîktê were bikaranîn ji ber ku me gelek tercime ji en.wîktê kopî kir û ev js hîn nikare formata wekî li jêr bi kar bîne.
<syntaxhighlight lang="wikitext">
* {{Z|sh}}:
*: Kirîlî:
*: Latînî
</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Mediawiki:Gadget-linkify.js]] ==
* ''rûpeleke'' wekî<syntaxhighlight lang="wikitext">[[rûpeleke]]</syntaxhighlight> lînk dike, bi regexê belkî dikare wisa bike <syntaxhighlight lang="wikitext">[[rûpel]]eke</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
* Amûreke din wekî [[User:Wikihez/Gadget-linkify.js|linkify]] lazim e ku herfên mezin biçûk bike ji bo kurmancî.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:{{r|Bikarhêner}} Merheba bira, dikarî viya jî bar bikî?:
* [[Special:PermanentLink/6291974|linkify]] --> [[Mediawiki:Gadget-linkify.js]]
Spas--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:42, 29 tîrmeh 2026 (UTC)
:Dûre [https://ku.wiktionary.org/wiki/MediaWiki:Gadget-start.js#L-283 li vir] wiktviLinkify bike kuLinkify xêra xwe [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:51, 29 tîrmeh 2026 (UTC)
::Min ji bîr kir lê zêde bikim niha nikare "Dİ" bike "di", xeletiyeke berbelav a di nivîsên kurdî de, herfên din ên wekî ll yan rr jî hewce ne werin zêdekirin. Dûre em zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:42, 29 tîrmeh 2026 (UTC)
:::Min nû ferq kir, sayfeyên ku hîn nehatine çêkirin wîkîlînk nake. Problemeke mezin e divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:15, 30 tîrmeh 2026 (UTC)
::::Na, lînk dike, tiştekî din heye... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:49, 31 tîrmeh 2026 (UTC)
== Amûra manayê ==
Amûreke din ji bo nivîsina maneyê wekî Nimûne zêde bike lazim e [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6276876 js] [https://ku.wiktionary.org/w/index.php?title=%C5%9Eablon:ceribandin2&oldid=6265629 şablon] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6265642 css] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:12, 23 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-jeder.js]] ==
@[[Bikarhêner:Ghybu|Ghybu]] Çavkaniyên jêderê di moda pêşdîtinê de jêderê vedişêre, ew jî min gelek eciz dike wextê çavkanî zêde dikim. Min ji Claude pirsî û kodan nîşanî wê da, wisa cewab da:
Yes — and this file actually explains the whole thing precisely, better than my earlier guess about extract-stripping.
What's happening: the CSS rule
<syntaxhighlight lang="css">
.jeder{
display:none;
}
</syntaxhighlight>
hides every citation by default, *unconditionally*. The only thing that ever makes it visible again is the gadget's JS: it finds each `.jeder`, builds the short "`— (year, author, +)`" summary from the `.j-pewist` fields, and either reveals the short version or — for citations with no `.j-pewist` fields — explicitly sets `display:inline` on the original.
So visibility of citations depends entirely on this JS running. In the normal article view (Image 1) it runs, so you see the collapsed "`(1918, E. Rehmî Ji Mala Hekariyan, Amadekar: M. E. Bozarslan, +)`" form. In whatever generated Image 2 — a hover-preview/extract/API render — gadget JS doesn't execute, so nothing ever removes the `display:none`, and the citation vanishes completely rather than degrading to "still visible, just not collapsed."
<b>The fix:</b> flip the default so the *un-collapsed* state is the safe fallback, and let JS do the hiding only after it successfully builds the short version. That way, any context without JS shows the full citation instead of nothing.
CSS — remove the blanket hide:
<syntaxhighlight lang="css">
.jeder {
/* intentionally no display:none here — see Gadget-... .js */
}
</syntaxhighlight>
JS — do the hiding from script, after the short form exists:
{{veşêre-ser}}
<syntaxhighlight lang="js">
// Ji bo şablonên [[Şablon:jêder]] hatiye nivîsandin.
// Parametreyên ku di navbera "<span class="j-pewist">...</span>" an "{{j-pêwîst|...}}" de ne venaşêre.
$(function() {
function nivisaKurtTevliBike(sund, nivisKurt) {
var zede = $('<a>+</a>')
.addClass('click')
.attr('href', '#')
.attr('title', 'zêdetir')
.css('font-weight', 'bold');
var kurt = $(' <a>↩</a>')
.addClass('click-kurt')
.attr('href', '#')
.attr('title', 'veşêre')
.css('font-weight', 'bold');
$('<span>' + nivisKurt + '</span>')
.append(zede).append(')')
.addClass('jeder-kurt')
.css('font-size', '0.85em')
.insertAfter(sund);
sund.append(' ').append(kurt);
// Only hide the full citation once the short summary actually exists.
// If this script never runs (no-JS clients, hover-preview extracts,
// action=render, etc.), the full citation stays visible instead of
// silently disappearing.
sund.css('display', 'none');
}
function vesere() {
$('.jeder').each(function() {
var elmTab = $(this).find('.j-pewist').clone();
if (elmTab.length) {
var elm = '';
for (var i = elmTab.length - 1; i >= 0; i--) {
elm = elmTab.eq(i).html() + ', ' + elm;
}
var nivisKurt = ' — (' + elm;
nivisaKurtTevliBike($(this), nivisKurt);
}
// else: no j-pewist fields to shorten — leave the full citation
// as it already renders (visible), nothing to do.
});
}
vesere();
$(".click").click(function(e) {
e.preventDefault();
$(this).parent('.jeder-kurt').css('display', 'none');
$(this).parent('.jeder-kurt').prev().css('display', 'inline');
});
$(".click-kurt").click(function(e) {
e.preventDefault();
$(this).parent('.jeder').css('display', 'none');
$(this).parent('.jeder').next().css('display', 'inline');
});
});
</syntaxhighlight>
Net effect:
- JS-enabled readers (normal article view): identical experience to today — collapsed short form with `+`/`↩` toggle.
- No-JS contexts (previews, extracts, screen readers before JS loads, print, etc.): full citation is shown instead of nothing — a real regression fix rather than a cosmetic one.
One caveat worth being upfront about: this fixes it *if* the preview context renders the gadget's HTML/CSS but doesn't execute gadget JS (which is the far more common case, and what this code strongly suggests is happening). If Image 2 instead comes from a pipeline that strips the `.jeder`/`.jegirtin-text` elements out of the HTML entirely before this CSS/JS even gets a chance to apply, no on-wiki CSS/JS change can help — that'd need the extract-generation config changed instead. Given what this gadget file shows, though, I'd bet on the CSS-hide explanation.
{{veşêre-bin}}
Em dûre biceribînin eger xeletî tinebe, vî tiştî jî rast bikin. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:00, 23 tîrmeh 2026 (UTC)
:Ez APIya MediaWiki rind nizanim belkî Claude dizane:) Em nikarin bibêjin dema ku moda "''action=edit''" e fonksiyona <code>function vesere()</code> bi kar meyîne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:57, 24 tîrmeh 2026 (UTC)
::Na, di moda "edît"-ê de <code>var elmTab = null</code>... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:13, 24 tîrmeh 2026 (UTC)
?:
<syntaxhighlight lang="js">
if (
mw.config.get('wgAction') === 'edit' ||
mw.config.get('wgAction') === 'submit'
){
var elmTab = [];
} else {
var elmTab = $(this).find('.j-pewist').clone();
}
</syntaxhighlight>
--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:31, 24 tîrmeh 2026 (UTC)
:Ez jî nizanim Claude çiqas ji mediawiki fêm dike lê heta niha ti tişt nîne ku min jê pirs kir û nikaribû çêke, bila wekî reklam nebe feqat Claude mikemmel e, dibêjim "li vir çima xeletiyeke wisa derdikeve" di dosyeya 2000 rêzî de diçe tiştê xelet tenê rast dike tiştekî din naguherîne. Chatgpt û aistudio qet wisa nakin, eynî dosyeyê deh cara dinivîsin, deh netîceyên cuda derdikevin :D
:Em dikarin vê amûrê wextê edit û submitê neaktîv bikin wekî <syntaxhighlight lang="js">if (!(action === "edit" || action === "submit")) {
:return;</syntaxhighlight> lê wê demê display:none wê çibe nizanim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:23, 24 tîrmeh 2026 (UTC)
::display:none ne hewce ye ma jixwe di nav kodê [https://ku.wiktionary.org/w/index.php?title=MediaWiki:Gadget-jeder.js&oldid=6069025#L-49 L-49] de inline/none heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:53, 24 tîrmeh 2026 (UTC)
:Min bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.js&diff=prev&oldid=6290816#L-28] ceriband feqat nexebitî. "display:none" di pêşdîtinê de dixebite feqat js naşixule. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:43, 25 tîrmeh 2026 (UTC)
::Kodên JSê pir hez nakim (pêşta hukurina APIya MWyê hewce bû) dîsa jî ez ê seh bikim :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:49, 27 tîrmeh 2026 (UTC)
:::Ez niha bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6290820] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6290821#L-8] bi kar tînim ti problem nîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:09, 27 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-PagePreviews.js]] ==
Ez dibêjim em vê amûrê jî çêkin [[:en:MediaWiki:Gadget-PagePreviews.js]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:56, 3 tebax 2026 (UTC)
= Moda tarî =
== [[Şablon:wîkîpediya]] ==
Rengê tarî de xirab e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:36, 4 tebax 2026 (UTC)
= Bot =
Karên ku bi botê werin çêkirin ji kerema xwe li vir binivîsin.
== Kategorî:Biwêj ==
[[:Kategorî:Biwêj]] divê were paqijkirin, lîsteya madeyên {{ş|biwêj}} çêke, yên ku tê de bûn, kirin, man hene filtre bike yên din bi destî tê çêkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Jêgirtin ==
Xistina jêgirtinên beşa [https://ku.wiktionary.org/w/index.php?search=insource%3A%2F%3D+*Ji+%5BWw%5D%C3%AAjey%C3%AA+*%3D%2F&title=Taybet%3AL%C3%AAger%C3%AEn&profile=advanced&fulltext=1&ns0=1&searchToken=8rh0bmw8dccsi7aynl6zli8uf ji wêjeyê] li beşa maneyê. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Bota Toolforgeê ==
Botekî Toolforgeê lazim e ji bo ku xeletiyên biçûk wekî şablonên bi navên îngilîzî werin kurdîkirin yan jî valahiya beşan rast bike û hwd.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:Min [[Bikarhêner:Wikihez/skrîpt/py/kuwiktkozmetik.py|paqijiyen kozmetîk]] hazir kir feqat hîn navên şablonan naguhêre. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 4 tebax 2026 (UTC)
== Updatew ==
Bota [[Bikarhêner:Wikihez/skrîpt/py/updateW.py|updateW]] divê were rojanekirin. Yan jî yekî nû were çêkirin. Şablonên {{ş|W}} di beşa tercimeyan de sererast dike û zimanan rêz dike. Ya niha xeletiya navê zimanî dide.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== sihir --> sêhr ==
Tercimeyên ''[[sêhr]]'' hemû dibêje ''sihir'' loma bi botê lazim e kirin ''sêhr''. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:43, 18 tîrmeh 2026 (UTC)
:Sihirbaz --> sêhrbaz [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 18 tîrmeh 2026 (UTC)
== Tercimeyên dîroknas ==
Li tercimeyên [[dîroknas]], hevmaneyên wê hewce ye were îlawekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:44, 18 tîrmeh 2026 (UTC)
== Mewlûd ==
Sererastkirina jêgirtinên mewlûdê [https://ku.wiktionary.org/w/index.php?search=insource%3A%2Fw%5C%3AMela+His%C3%AAn%C3%AA+Batey%C3%AE%2F&title=Taybet%3AL%C3%AAger%C3%AEn&ns0=1&searchToken=8m0tguy3sqt7edr4sg0u8hedo] wekî [https://ku.wiktionary.org/w/index.php?title=dax%C3%AEdar&curid=284303&diff=6265851&oldid=6142285] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:43, 20 tîrmeh 2026 (UTC)
== [[Şablon:jêder]] ==
Şablon jêder axlebî bi şiklê MM/YYYY sal tê de heye, bi botê em dikarin wana bixin {{para|sal}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:07, 17 tebax 2026 (UTC)
hl51gppscs9rqee2hls6iolg8w53pwj
6334374
6334372
2026-08-17T18:03:11Z
Wikihez
7344
/* MediaWiki:Gadget-start.js */ Bersiv
6334374
wikitext
text/x-wiki
{{/ser}}
= Karên ferhengî =
== Mane ==
* [[:Kategorî:Madeyên bê beşa mane]]
** [[:Kategorî:Madeyên bê beşa mane bi kurmancî]]
Li ser van rûpelan ev made hene ku maneyan wan nehatine nivîsin û divê werin nivîsin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Etîmolojî ==
*[[:Kategorî:Daxwazên etîmolojiyê]]
**[[:Kategorî:Daxwazên etîmolojiyê bi kurmancî]]
Li ser van rûpelan ev made hene ku etîmolojiya wan hîn nehatine nivîsin. Ji bo alîkariyê binêre [[WF:Etîmolojî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Cureyê rêzimanî ==
* [[:Kategorî:Gotarên bê kategoriya rêzimanî]]
* [[:Kategorî:Gotarên bê kategoriya rêzimanî bi kurmancî]]
Li ser van rûpelan ev made hene ku cureyên wan yên rêzimanî hîn nehatine nivîsin. Ji bo alîkariyê binêre [[Alîkarî:Cureyên peyvan]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Kontrol ==
* [[:Kategorî:Kontrol]]
** [[:Kategorî:Kontrola maneyê]]
Ev madde bi botê hatine çêkirin, yek bi yek divê werin kontrolkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
= Şablon =
== IPA ==
* {{ş|IPA}} divê [[Modul:IPA]] bi kar bîne. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Deng ==
Kurdîkirina belgekirina {{ş|deng}} lazim e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 19 tîrmeh 2026 (UTC)
= Modul =
== Modul:form of/templates ==
* [[Modul:form of/templates]]: Divê ev modul were rojanekirin û xeletiyên berê wekî ([[hevalê]]) werin rastkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:Merheba @[[Bikarhêner:Ghybu|Ghybu]] Te gotibû di vê modulê de xeletî hene, jkx tu dikarî li vir binivîsî çi kêmasî hene, daku wextê rojane dikim ez ji bîr nekim? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:16, 21 tîrmeh 2026 (UTC)
*::Silav. Nizanim ji bîra min çûye... Herwiha ji bo çêkirina formên navdêran zanînên cîns û tîpa dawî bes e:
*::* (-ê, mê) => çemandî, bangkirin
*::* (-ê, nêr) => îzafe
*::* (-ê, mê û nêr) => îzafe (nêr), bangkirin (mê), çemandî (mê)
*::* (-a) => îzafe (mê)
*::* (-ekî) => îzafe û çemandî (nêr, nebinavkirî)
*::* (-eke) = > îzafe (mê, nebinavkirî)
*::* ...
*:: <code>if find(mw.title.getCurrentTitle().subpageText, 'ekî$') then ... end</code>
*::<code><nowiki>{{ku-tewîn-navForm|<navdêr>|<zayend>}</nowiki></code>
*::Tablo û nivîs di yekcarî de tên çêkirin...
*::Feqet problem lêzêdekirina mînakan e. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:34, 21 tîrmeh 2026 (UTC)
*:::Lêzêdekirina kîjan nimûneyan? Ev koda js ji bo çi ye? Qisûrê nenêre min fêm nekir :/ [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:13, 21 tîrmeh 2026 (UTC)
*::::Li vira ([[hevalê#Formeke navdêrê]]) di bin maneyên 1. û 2. mînak nayên tevlî kirin :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 21 tîrmeh 2026 (UTC)
*:::::Ev tercîh e, ne mecbûrî ye, em dikarin [https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&diff=prev&oldid=6276350 wisa] bikin. Wekî din? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:29, 21 tîrmeh 2026 (UTC)
*::::::Min li jor tênê got ku tenê parametreyên "navdêr" û "zayend" hewce nee. Bi '''[//ku.wiktionary.org/w/index.php?title=heval%C3%AA&curid=969734&diff=6276353&oldid=6276350 vî]''' awayî jî tê çêkirin. Parametreyên din ne hewce ne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:38, 21 tîrmeh 2026 (UTC)
*:::::::Ji bo wê şabloneke din lazim e, bila ev şablon wekî îngilîzî bimîne em ji bo kurmancî yekî din çêkin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:42, 21 tîrmeh 2026 (UTC)
*::::::::[https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&oldid=6276885 wisa] çawa ye? Bi min nivîsîna nimûneyan zêde ne lazim e ji ber ku ti kes serê xwe pê naêşîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:46, 23 tîrmeh 2026 (UTC)
*:::::::::@[[Bikarhêner:Ghybu|Ghybu]] Vê şablonê bi kar bînin an na? Navê şablonê kê çi be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:07, 24 tîrmeh 2026 (UTC)
== Modul:columns ==
* [[Modul:columns]] were rojanekirin û {{ş|kol3}} bila lînka ''nîşan bide'' deyne beriya sernavê, kolê di qutiya rengî de bila nebe. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:etymon ==
* [[Modul:etymon kevn]]: Rojanekirin hewce ye. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Modul:ziman/code to canonical name]] ==
Belgekirina skrîptê û js divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:31, 22 tîrmeh 2026 (UTC)
:* [[MediaWiki:UpdateLanguageNameAndCode.js]]
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:50, 22 tîrmeh 2026 (UTC)
::* [[Modul:ziman/print]]
::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:52, 22 tîrmeh 2026 (UTC)
:::*[[Modul:ziman/şablon]]
:::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:56, 22 tîrmeh 2026 (UTC)
::::* [[MediaWiki:Gadget-translation editor.js/ziman.json]]
::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:06, 22 tîrmeh 2026 (UTC)
== [[Modul:quote]] ==
Piştî ev modul hate hazirkirin, lazim e navên jegirtinan werin rastkirin û li hemûyan {{param|jêgirtin}} were zêdekirin. Bnr. [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Mem û Zîn]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 5 tebax 2026 (UTC)
::Ev modul hema bêje hazir e. Hûn li ser van madeyan çi difikirin:
# {{para|tarîx}} lazim e full date be eger na, lazim e {{para|sal}} were bikaranîn. Feqat "'''2021''' kanûna pêşîn 23" bi vî şiklî bi min ne problem e çnkî derdê en.wikt "attestation" e delîla herî pêşî jî sal e.
#: {{demo|sep=wisa xuya dibe<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|tarîx=23 December 2021|nivîskar=Harry Hobbs; George Williams|sernav=Micronations and the Search for Sovereignty|url=https://www.cambridge.org/core/books/micronations-and-the-search-for-sovereignty/B0414A3A322695D9EE71E4CDDFDA06DE|weşanxane=Cambridge University Press|rûpel=155|isbn=978-1-009-15012-5|jêgirtin=jêgirtin.|t=-}}</nowiki>}}
#{{para|amadekar}}/{{para|edîtor}}/{{para|terciman}} bi şiklê edîtoriya, amadekariya, tercimekirina
#: {{demo|sep=dide<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|sernav=Medical Surgical Nursing|serî=Juta's Manual of Nursing|cildê-seriyê=volume 4|edîtor=Sophie Mataniele Mogotlane; Joyce Mokoena; Motshedisi Eunice Chauke; Anne Young|sal=2005|weşanxane=Juta Academic|isbn=978-0702171840|rûpel=!12-29|jêgirtin=jêgirtin|t=-}}</nowiki>}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:09, 10 tebax 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Hîn gelek tişt hene divê îzah bikin û fikrê we pirs bikim, feqat îzahkirin pir zor e, em dest bi bikaranînê bikin û dûre tiştên ku hûn naxwazin em dikarin biguherînîn, dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:11, 10 tebax 2026 (UTC)
::Dibe, piştî çêbû, em di praktîkê de bibînin ku çawa ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:05, 10 tebax 2026 (UTC)
= Javascript =
== [[Mediawiki:Gadget-citoid-helper.js]] ==
Ji bo bi citoidê kopîkirina malûmatên çavkaniyan amûreke wisa lazim e. Xisûsen ji ber ku bi çend amûrên din dikare were bikaranîn.
Feqat çend problem hene, şablonên me de kêmasiya parametreyan gelek in û xerîteyên citoidê hîn nehatine çêkirin. Wana sererast bikin dûre vê amûrê çêkin, piştre [[MediaWiki:Gadget-start.js|start.js]] û [[MediaWiki:Gadget-wikt.add-examples.js|add examples.js]] were rojanekirin.
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:04, 19 tîrmeh 2026 (UTC)
:Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]], ez dibêjim karê Modul:quote zêde wext bistîne, bila em dest bi bikaranîna van amûran bikin dûre kêmasiyan em ê temam bikin înşela. Dikarî vana li cihên xwe yên rast kopî paste bikî?
# [[Special:PermanentLink/6291574|Bikarhêner:Wikihez/skrîpt/js/Gadget-citoid-helper.js]] --> [[Mediawiki:Gadget-citoid-helper.js]]
# Ya li jêr kopî bike qismê herî dawî ya AmûrênVeşartî li [[MediaWiki:Gadgets-definition]]:
## <code><nowiki>* citoid-helper[ResourceLoader|hidden|default|dependencies=mediawiki.util]|citoid-helper.js</nowiki></code>
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:58, 26 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-start.js]] ==
Rojanekirin hewce ye. Min gelek tişt lê zêde kir lê ez niha test dikime. Piştre em bar bikin:
* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 17 tîrmeh 2026 (UTC)
:Min [[MediaWiki:Gadget-start/data.json|data.json]] çêkir ji bo ku bi hêsanî malûmatên heyî werin guhartin bêyî guhartina Javascriptê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:49, 18 tîrmeh 2026 (UTC)
:Xeletiyên ku ji bîr nekim rast bikim:
:* Wextê min çavkanî "kitêb" hilbijart û bi destî tije kir xeletiyeke [https://ku.wiktionary.org/w/index.php?title=xezete&diff=prev&oldid=6265392 wisa] derket her kelîme hate lînkkirin.
:* <code>html("⠿")</code> ev ne lazim e.
:* textarea ji bo çavkaniyên jêgirtinan xaseten yên sernav hîn jî ne draggable e.
:* pêşdîtina zindî checkbox wextê tê rakirin, pêşdîtin winda nabe.
:* Butona mobîlê ya vekirina formê di moda tarî de belî nabe, tenê wekî qutiyeke tarî xuya dibe.
:* Metna hişyarkirinê di guhartina rûpelên heyî de lazim e baştir bikim.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:51, 18 tîrmeh 2026 (UTC)
::Ev problem çêbûn, ez ê binêrim ka çi problemên din hene. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:15, 19 tîrmeh 2026 (UTC)
:::Çêke: Ji bo qismê çavkaniyê yek dikare API call û Kategorî:Çavkanî bi kar bîne ji bo ku şablon zêde bikî ji yên heyî. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 19 tîrmeh 2026 (UTC)
::::Ev jî çêbû, min beşa çavkanî jî çêkir. Eger xeletî dernekeve û problema valahiya piştî etîketê ji bîr nekim, piştî rojanekirina amûrên linkify, û citoid em dikarin bar bikin. Ez ê ti tiştê nû îlawe nekim, zêde dirêj bû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:14, 21 tîrmeh 2026 (UTC)
:::::Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]]. Dikarî wana bar bikî?
:::::* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
:::::* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
:::::* [[MediaWiki:Gadgets-definition]] wisa rojane bike:
:::::* start[ResourceLoader|default|package|dependencies=mediawiki.util,ext.gadget.StorageUtils,ext.gadget.citoid-helper]|start.js|start.css|start.js/data.json
:::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:39, 5 tebax 2026 (UTC)
{{şirove}} Dewama ''[[Special:PermanentLink/6333688|Gotûbêja bikarhêner:Rewiye tine#WF:Nû]]'' bila gotûbêja wî em spam nekin. :D
{{r|Ghybu}} dikarî [[Special:PermanentLink/6333689|vê htmlê]] binêrî claude rast fêm kiriye yan na? Daxîne û wek html qeyd bike. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:43, 14 tebax 2026 (UTC)
:Min seh wîkîyên din jî kir, li wîkiya fransî tiştekî wekî daxwaza min çêkirine (tiştên ku ne hewce ne wekî "tab" çêkirine an jî hatine vêşartin): [//fr.wiktionary.org/w/index.php?title=fdsdfdsf&action=edit&redlink=1]. Formata me yê berî jî bi vê lojîkê hatibû çêkirin, wekî de.wiktionary ([//de.wiktionary.org/w/index.php?title=sdfdsfsdfs&action=edit&preload=Vorlage:Formatvorlage_(Substantiv_vereinfacht)]) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:01, 14 tebax 2026 (UTC)
::PS: Dîsa jî Claude min rind fêm kiriye :)) Feqet min got ku beşên Mane", "Bilêvkirin" û "Beşên din" cem hev (wekî "tab") ". Û dema ku tu terî di nav "Besên din" Hevmane, Jê, ... li wira werin dayîn. Wêne/Resim û Nimûne jî di nav beşa ("tab") "Mane" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:17, 14 tebax 2026 (UTC)
::Erê ya fr.wikt min jî dîtibû jixwe fikrê tab min ji wan girtibû :D (feqat min li cem wan tenê tab-a maneyê dît yên din nedît çawa tê vekirin nizanim) Berê hemû beş piştî maneyê bibûn, dûre min sê Tab çêkir, Bingehîn+Lînkkirin+Bikaranîn. Niha lazim e Tabeke din çêkim. Ya ku claude çêkiriye bnr. [[Special:PermanentLink/6333698#L-7]] ne baş e bnr. [[Special:PermanentLink/6333703|qiyaskirinê li ser vê htmlê]] Êdî ez Claude abuse dikime ji ber ku Pro ye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:26, 14 tebax 2026 (UTC)
:::Claude dibêje:
:::Good clarification — that's a real simplification, not just a relabel. Updated STEP_GROUPS from 5 tabs down to 3:
:::Mane — the definitions themselves, plus Wêne (image), now grouped in with it instead of sitting in a separate misc tab. Nimûne needed no change since it was already rendering inside each Mane sense block, not as its own step.
:::Bilêvkirin — unchanged, still just pronunciation.
:::Beşên din — everything else in one flat bucket: hevmane, jê, têkildar, herwiha binêre, formên alternatîv, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, tewandin, etîmolojî, zaravayên din, werger, and bnr-h — instead of being split across the old Bingehîn/Lînkkirin/Bikaranîn/Din tabs.
:::No DOM-order changes were needed this time — the fix from the blank-tab bug looks up the tab bar's position dynamically now, so it didn't matter that I regrouped which steps go where. Same two files, saved to your Wiki folder — worth another pass through all three tabs once you've pasted this in, same as last time. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:34, 14 tebax 2026 (UTC)
::::Mînak:
Ziman
---------------------------------------------
Cureyê rêzimanî (Navdêr)
Navdêr (hilbijêre) ┆ Cîns (hilbijêre) ┆ Jimar (hilbijêre)
- lêker -mê -yekjimar
- rengdêr -nêr -pirjimar
- ... -... -...
---------------------------------------------
Mane || Bilêvkirin || Beşên din
hilbijêre:
- mane - IPA - Hevmane
- nimûne - kîte - Dijmane
- wêne
Dema ku tu "Cureyê rêzimanî" dikî "Lêker" :
Cureyê rêzimanî (Lêker)
Lêker (hilbijêre) ┆ Cure (hilbijêre)
- navdêr -rehê niha
- rengdêr -rehê borî
- ... -...
---------------------------------------------
┆ : select/option ("tab" nîne, tê dîtin)<br>
|| : "tab"
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:45, 14 tebax 2026 (UTC)
'''Sê-stûnî ("┆" = ne tab), li gorî Cureyê rêzimanî:'''
{| class="wikitable"
! Cureyê rêzimanî !! Stûna 2 !! Stûna 3 !! Têbînî
|-
| Navdêr / Serenav
| Cîns (hilbijêre ▾: mê / nêr / ... — tenê heke ziman zayendê rêzimanî hebe)
| '''Jimar''' (?) — pirs li jêr
| Qadên "Forma mê/nêr/pirjimar/yekjimar" niha jî li vir hene — ma ev di "Jimar"-ê de tên tevlihev kirin, an cuda dimînin?
|-
| Rengdêr
| (heman m/n/p/y forman ku Navdêr distîne)
| Sûperlatîv (sûp) / Komparatîv (komp)
| Ne li ser her du nîgaran hatiye behs kirin — ma sê-stûnî li vir jî tê bikaranîn?
|-
| Lêker
| '''Cure''' = Cureyê lêkerê (hilbijêre ▾: gerguhêz / negerguhêz / xwerû-... / pêkhatî-... / hevedudanî-... / biwêjî-... — 10 vebijêrk)
| Di heman stûnê de: Rehê dema niha / Rehê dema borî / Rehê dema bilanî (her sê qadên nivîsandinê ne, ne hilbijêrk in)
| Ma "Cure" her sê rehan di xwe de digire wek yek stûn, an her yek stûneke cuda ye?
|-
| Tîp (herf/sembol)
| Girdêk (gir) / Hûrek (hûr)
| —
| Ne behs kiriye
|-
| Her cureyek, li gorî zimên
| Transliterasyon (tr) / Transkrîpsiyon (ts)
| —
| Bi zimên ve girêdayî ye, ne bi cureyê rêzimanî ve
|-
| Her cureyek, tenê ziman=japonî
| Hanja / Translit. hanja
| —
| Ne bi cureyê rêzimanî ve girêdayî ye
|-
| Her cureyek
| '''Tercîhên pêşketî''' (kolapsî): form, formaalt, nekatcure, nekatcins, nekatçendterm, serlînkneke, nekatpalîndrom, neformtr
| —
| Vebijarkî, ji bo hemû cureyan
|}
'''Tabên jêrîn — Mane ‖ Bilêvkirin ‖ Beşên din ("||" = tab):'''
{| class="wikitable"
! Mane !! Bilêvkirin !! Beşên din (hemû 18 hêman)
|-
| mane, nimûne, wêne
| şablonên bilêvkirinê (mînak: IPA, kîte)
| tewandin, etîmolojî, bi zaravayên din, werger, jê, têkildar, herwiha binêre, formên alternatîv, hevmane, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, bnr-h
|}
'''Pirsên vekirî:'''
# "Jimar" (yekjimar/pirjimar) — qadeke ku nîşan dide têketî bi xwe yekjimar e an pirjimar e. Ev cuda ye ji "Forma yekjimar (y)"/"Forma pirjimar (p)" ya heyî (ku forma DÎN a kelîmeyê tomar dike). Herdu bimînin, an werin yek kirin?
# Ma sê-stûnî tenê ji bo Navdêr/Lêker e, an ji bo Rengdêr/Tîp jî heman mantiq derbas dibe?
# Di "Beşên din" de: dixwazî pêşî navnîşek/hilbijartinek nîşan bide (17 hêmanên din jî tê de), û qad tenê piştî hilbijartinê xuya bibin?
:Tu dikarî start.js ji Tercîh/ Amûran rakî û bi test.js li Amûran herî taliyê ye biceribînî. Claude ya li jor hazir kir, Jimar ez dikarim îzah bikim. Feqat bi min di tab-a maneyê de lazim e Bi zaravayên din (heta wek werger her car bila li rûpelan were zêdekirin) û werger jî hebe.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:18, 14 tebax 2026 (UTC)
:;Navdêr: Tenê "cîns" û "jimar" bes e. "Forma mê", "Forma nêr", ... nayê fêm kirin. Bi min wekî "Tercîhên pêşketî" bi navkî din veşartî çêke.
:;Lêker: "Cureyê lêkerê" nayê fêm kirin, bi min pêşta "geguhez/negerguhêz" were hilbijartin, paşê jî form "hevdudanî/pêkatî/..." ji ber ku her kes vana nizanî... Dîsa jî tu dizanî.
:Bi min beşên "Hevmane" û dijmane jî bike de nav beşa Mane ("tab") ji ber ku pirr tên xwestin.
:Şûnda Wêne jî Nimûne/jêgirtin...
:Herwiha hemû bi rengê şîn hatine çêkirin, nizanim beşên din (===) bi rengê gewr an reş çito dibe? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:35, 14 tebax 2026 (UTC)
::Nimûne/jêgirtin, hevmane, Dijmane li binê her maneyê hene. ji ber ku wana bi maneyê ve girêdayî ne lazim e wisa bimîne feqat belkî şiklekî baştir bibînin. Hînbûna bikaranîna formeke nû bi qasî hînbûna koda wîkiyê zor e heqîqeten... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 14 tebax 2026 (UTC)
:::Min şaş fêm meke feqet te amûrek ji bo Wikihez çêkiriye :) Tu dixwazî ku hemû tiştên ku te çêkiriye tevlî vê Gadgetê bikî û bikarhênerên din jî bi kar bînin... Tu jî dizanî ku kesên ku tên vira çito peyv çêdikin: Ziman > Mane > Mînak > Hevmane > Dijmane ! Du Gadget çêke yek ji bo bikarhênerên ku hertim li vira ne, yekeke din jî ji bo bikarhênerên din (yên ku pirr nên vira).
:::Herwiha kesên ku hertim li vira ne bi Gadgetê gotar çênekin, ''copy/paste'' dikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 14 tebax 2026 (UTC)
::::Min xwest her tişt li ser rêza xwe be ji bo ku hîn bibin çi diçe kû derê feqat bi vî şiklî jî zêde dirêj û tevlihev dibe. Loma ez ê wekî tu dibêjî bikim her tiştê din bila collapsed pêşketî be. Ê ku dixwaze pê lê ke û binêre çi dikare were zêdekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 14 tebax 2026 (UTC)
:::::Niha baştir tê fêm kirin {{=)}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:01, 14 tebax 2026 (UTC)
::::::Min ji Opus 5 û Design Tool îstifade kiriye hema bêje milyonek token çûn :D pêşî dîzayn kir dûre dest pê kir wekî dîna bişuxile. Pereyê xwe %100 heq dike welleh :D feqat moda tarî ji bîr kiriye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:12, 14 tebax 2026 (UTC)
:::::::Spas! Etîmolojî jî wekî "Bilêvkirin", di beşekê din de cem wê, were çêkirin (Mane|Etîmolojî|Bilêvkirin|Beşên din)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:20, 14 tebax 2026 (UTC)
::::::::(Mane>Tewandin>Werger|Etîmolojî>Dûnde>|Bilêvkirin(formatek çawa?)>Resim|Eleqedar>Jê>Têkildar>Herwiha binêre|Beşên din)? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:29, 14 tebax 2026 (UTC)
:::::::::Nizanim, biceribîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:43, 14 tebax 2026 (UTC)
::::::::::"Li şablonekê bigere" ser Maneyê hatiye nivîsandin, nizanim rind tê fêm kirin an na? Min paşê fêm kir :)) Naskirina Wîkîferhengê hewce ye... Li bin were nivîsandin û "notek" hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:02, 15 tebax 2026 (UTC)
:::::::::::"Malûmatên pêşketî nîşan bide" jî bike ser "Hevman" ji ber ku pirr li jor e. Tu nizanî çi dike (nayê dîtin)... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:09, 15 tebax 2026 (UTC)
::::::::::::Dever/Devok jî were nivîsandin ? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 15 tebax 2026 (UTC)
:::::::::::::Tistekî din, dema ku tu maneyê dinivîsî bi otomatîk li jêr mane "2" tê nivîsandin, ew jî ne baş e (pirranî ne hewce ye). Bi min wekî "tab" cem "Mane 1" were vekirin (MAne1|Mane 2|Mane 3), bi otomatîk û an jî bi tikandina ser "Maneyeke din lê zêde bike" (cem beşa Maneyê). An jî bin beşa Wergerê bi nivîsa "maneyeke din lê zêde bike" were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:44, 15 tebax 2026 (UTC)
::::::::::::::Ji bo mane2, mane3, ... problemek heye: "Beşên din" ji bo kîjan maneyê ye :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:49, 15 tebax 2026 (UTC)
:::::::::::::::Devok/dever ya rastî ji bo kurmancî herî muhîm e lê diçe qismê etîketê ew jî veşartî ye, Niha çawa çêbûye? Min butona +mane zêde bike îlawe kiriye. Beşên din ji bo hemû maneyan e, ji bo maneyên xisûsî lazim e yek qismê malûmatên zêdetir bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:01, 15 tebax 2026 (UTC)
::::::::::::::::mane1, man2, ... cem beşa "Mane" çito ye, wekî:
::::::::::::::::<poem>
:::::::::::::::: '''Mane''' || Etîmoljî || Eleqedar || Beşên din
:::::::::::::::: -----------------------------------------------------------------
:::::::::::::::: Mane 1 || '''Mane 2''' || + Mane zêde bike [[Wêne:OOjs UI icon trash-destructive.svg|18px]]
:::::::::::::::: <small>Maneya kelîmeyê binivîse. Mane otomatîk tê ...</small>
::::::::::::::::┌───────────────────────────────────┐
::::::::::::::::│ Kelîmeyên ji bo... │
::::::::::::::::└───────────────────────────────────┘
:::::::::::::::: -----------------------------------------------------------------
::::::::::::::::</poem>
::::::::::::::::Belkî çêkirin zor e... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:59, 15 tebax 2026 (UTC)
:::::::::::::::::Ev nimûne çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:05, 15 tebax 2026 (UTC)
::::::::::::::::::Te beşa "Bilêvkirin" kiriye cem "Beş1", erê. Li gor vê lojîkê "Etîmlojî" jî were cem beşa "Bilêvkirin", na? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:19, 15 tebax 2026 (UTC)
::::::::::::::::::Etîmolojî h4 e Bilêvkirin h3 loma min Bilêvkirin xiste cem "Beş1". Tu dibêjî îzaha qismê Maneyê "Maneya kelîmeyê binivîse. Mane otomatîk tê" li bin tabên Maneyê bin ne wisa? Pir ferq dike? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:23, 15 tebax 2026 (UTC)
:::::::::::::::::::Checkbox ji bo Etîmolojiya "Beş2" zêde bikim? Eger truthy be bila Etîmolojî h3 be û here pêşiya crz (cureyê rêzimanî em jî hinek kurtkirin bi kar bînin êdî) ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 15 tebax 2026 (UTC)
:::::::::::::::::::Na min tiştekî witira negot, min tenê ji bo fêmkirinê ew şemayê çêkir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:27, 15 tebax 2026 (UTC)
::::::::::::::::::::OK, niha rind bû :) "Maneyeke din zêde bike" çito ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 15 tebax 2026 (UTC)
::::::::::::::::::::Temam wê demê her tişt çêbû... Însan hîn dikare gelek tişt zêde bike, feqat usage limits hene :D Min session-a 5 saeatî temam kir. Dûre ez ê wisa bikim wextê pê li taba Mane-Etîmolojî dikî here jor, bi vî aawayî piştî dagirtina her tiştî li jêr însan dikare bi hêsanî maneyên din binivîse. Pişhingî jî ez ê paqiya zimanî bikim, piştî hinek ceribandinan em nû bikin. Eger fikrên nû were dûre dîsa dikarin îlawe bikin :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:35, 15 tebax 2026 (UTC)
:::::::::::::::::::::Temam! Dema ku tu "Mane 2" an "Mane 3" bi şaşî nivîsand... çito tê/n jêbirin? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 15 tebax 2026 (UTC)
::::::::::::::::::::::Vala bikî bes e, beşên vala dernakevin li outputê. butonek çêkin? li kû derê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:49, 15 tebax 2026 (UTC)
::::::::::::::::::::::Cem "Maneyeke din zêde bike" '''jêbibe''' were nivîsandin an jî wêneyê [[Wêne:OOjs UI icon trash-destructive.svg|22px]]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:51, 15 tebax 2026 (UTC)
:::::::::::::::::::::::Mane 1 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] Mane 2 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] +Maneyeke din zêde bike çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 15 tebax 2026 (UTC)
::::::::::::::::::::::::Nizanim kîjan nîşan baş e: va jî heye: <code>✕</code> [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:03, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::Bi min tenê yek carê were nivîsandin ji ber ku tu bi şasî maneyên xwe jî jê dibirî (dema ku tu li ser maneyê ditikînî) :) Li cem "Beş zêde bike" jî were nivîsandin [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::Bi kurdmancî "Rehê dema bilanî (subj)" ne hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:48, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::::Lazim e pêşî em şablonan li gorî zimanan çêkin wekî en.wikt {{şx|en-lêker}} dûre li gorî parametreyên TemplateDatayê em qutiyan dikarin zêde an kêm bikin. Niha Malûmatên şablonên etîmolojî, bilêbkirin û guharto hhwd. ji templatedayê dîstinê wextê pê li butona (i) dikî, lê gelek ji wan ne xwedî TemplateData ne. Hîn karên din jî hene divê em bikin wekî sererastkirina şablonên jêgirtinê. Loma niha wisa bimîne. Em li gotûbêja amûrê beşa Daxwaz çêkin? tiştên ku tên bîra me em li wir binivîsin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::::Min yên ku te gotine çêkir feqat bi min resma jêbirinê baş nebû, tenê ya talî tê jêbirin ne tiştekî normal e? Min Beşên din jî xiste nav Eleqedar, Beşên din ne hewce ye. Bila zêde tab jî nebin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:11, 15 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}}
Îkona jêbirinê (min rind fêm nekir) li gor daxwaza xwe bike... Herwiha çend bug hene:
# Di nav maneyê de nivîs "zû" nayê nivîsandin.
# Dema ku tu beşê dikî "Navdêr", paşê jî "Lêker": wekî "Navdêr" dimîne. Tu ê li ser "Bilêvkirin" bitikînî.
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:18, 15 tebax 2026 (UTC)
:Min 1 fêm kir, ji ber ku Werger û Bi zaravayên din tên updatekirin dereng dimîne, belkî li şûna wextê nivîsînê, em bikin piştî nivîsînê ew ê zûtir be.
:Ya 2an min nikaribû bibînim çawa ev problem derdikeve, li ku derê wekî Navdêr dimîne piştî guhartinê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:40, 15 tebax 2026 (UTC)
::Min fêm kir lazim e li Bilêvkirinê pê lê bikî da ku biguhêre... Ez ê rast bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 15 tebax 2026 (UTC)
:::Tiştekî din :) Formatên "Bilêvkirin" (kare) û "Beş1" (nîv-oval) ne wekhev in. Herwiha formatên "+ Beş zêde bike" û "+ Mane zêde bike" jî ne wekhev in. Tercihên te ne an jî bug e? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:37, 15 tebax 2026 (UTC)
::::Ev nîv-oval ji min re gelek ecêb tê. Yên din lazim in yek şekil bin wekî +Mane zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:40, 15 tebax 2026 (UTC)
:::::Li şûna "Ev ne pênase ye — şablonek bi kar bîne" ez ê çi binivîsim? Ti tişt nayê bîra min ya ku dikare vê bikaranînê îzah bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:43, 15 tebax 2026 (UTC)
::::::Maneyê bi şablonan çêke (rastnivîs, şas nivîs, aweyekî din, ku-Latn, ...) :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 15 tebax 2026 (UTC)
:::::::Bi min "rehê niha" û "rehê borî" were dîtin ji ber ku bê wan ew lêker nayê bikaranîn. Tiştekî pirr hewce ye... Te "Cureyê lêkerê:" nivîsandiye paşe jî bin wan "Rehên lêkerê (Lêker çito tê tewandin?):". Herwiha Beşa "Tewandin" hertim şablona "ku-tewîn-nav" dide.
:::::::(?) Dever/Dovok jî bin Manyê were nivîsandin: Ew pey li ku derê an di kîjan devokê de tê bikaranîn: ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:45, 16 tebax 2026 (UTC)
::::::::An jî di bin beşa "Tewandin"ê de vana pirs bike....r [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:56, 16 tebax 2026 (UTC)
::::::::Herkes nikare "rehê niha" û "rehê borî" bizane, eger em "dema niha" û "dema borî" bixwazin hêsantir tê fêmkirin û bi zimanên din jî tê bikaranîn. Feqat [[Modul:rêziman]] wana destek dide? Nayê bîra min... Ez ê dîzayna Dever/devok ji design toolê pirs bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:57, 16 tebax 2026 (UTC)
:::::::::Erê, bi parametreyên <code>niha=</code>/<code>nihatr=</code> û <code>borî=</code>/<code>borîtr=</code>. <code>subj=</code>Bnr, {{q|fa|داشتن}}, {{q|ku|emilandin}}, {{q|ku|emilîn}}. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:02, 16 tebax 2026 (UTC)
::::::::::Wana dizanim, ew reh in, ez dibêjim forma dema niha, forma dema borî, em fniha, fbila û fborî çêkin? Bi tirkî, îngilîzî jî dikare were bikaranîn... dikim, bikim, kir. yapiyorum, yap, yapti, doing, do, did? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:06, 16 tebax 2026 (UTC)
:::::::::::Ahh, min niha fêm kir :) Paşê jî li gor wan em dikarin rehê lêrê çêkin... Na ew nehatine çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:17, 16 tebax 2026 (UTC)
:::::::::::min ceriband [https://ku.wiktionary.org/w/index.php?title=yapmak&diff=prev&oldid=6334114] werga çitonî ye? Ez dibêjim zêdetir kes dikare forma dema niha bizane û binivîse feqat herkes nikare reh fêm bike çi ye û çawa ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:19, 16 tebax 2026 (UTC)
::::::::::::Bi min qutiya nivîsa Maneyê wekî yê "Nimûne" mezin were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:50, 16 tebax 2026 (UTC)
:::::::::::::Prompt-ên me pir zeîf in, lazim e pêşî jê re Promptek bidin çêkirin dûre herin qismê Codeê û promptê bizelîqînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:32, 16 tebax 2026 (UTC)
::::::::::::::Êdî AI promptên ne baş jî fêm dikin û serrerast dikin, pêşta hukirina formata promptan hewce bû... Pirr pirs hatine pirs kirin; niha, ew dizanin kîjan rind e û tu çi dixwazî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:45, 16 tebax 2026 (UTC)
:::::::::::::::Format niha baş e feqet ew Gadjet ji bo kesên ku debî-ya înternetê yên wan hêdî ye rind dişuxile? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:58, 16 tebax 2026 (UTC)
::::::::::::::::(ji bo lêkeran) Bi min, bin "Lêker derbasdar e?" were nivîsandin wekî "Tewandina lêkerê". Pêş qutiyan jî "ez" an "min" (borî) ( li gor curyê lêkerê were nivîsandin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:04, 16 tebax 2026 (UTC)
::::::::::::::::In this sentence, debî means "(data) throughput" or "bandwidth" — internet connection speed. It's a loanword from French débit (as in débit internet), which is used in Kurdish tech/web contexts for how fast a connection is.
::::::::::::::::So the phrase "kesên ku debî-yên înternetê yên wan hêdî ye" translates to "people whose internet speed/bandwidth is slow" — i.e., people on slow connections.
::::::::::::::::The full sentence is Ghybu asking: "The format is good now, but does this Gadget run well for people with slow internet connections?" — essentially a performance/loading-speed concern, not a formatting one.
::::::::::::::::If you want, I can look at what might be slowing the gadget down for low-bandwidth users (e.g., how many separate resources it loads, any heavy assets) and suggest something before you reply to them.
::::::::::::::::yes take a look
::::::::::::::::Read 6 files, ran 2 commands, searched 2 patterns
::::::::::::::::I dug into what actually gets downloaded on a fresh load of the wizard. Breakdown of the four files:
::::::::::::::::File Size (raw / gzip)
::::::::::::::::start.js 270 KB / 77 KB
::::::::::::::::data.json 114 KB / 36 KB
::::::::::::::::styles.css 87 KB / 19 KB
::::::::::::::::util.js 16 KB / 6 KB
::::::::::::::::So roughly 490 KB of source, ~140 KB after gzip — that's the real number Ghybu is feeling on a slow connection, especially the first time they load the gadget after any update (MediaWiki caches it aggressively afterward via a versioned URL, so it's mainly a one-time-per-version cost, not every page load).
::::::::::::::::Two concrete findings:
::::::::::::::::A real, fixable bottleneck: the whole gadget's initialization is gated behind mw.loader.using(["oojs-ui-core", "oojs-ui.styles.icons-moderation"], ...) — meaning the wizard doesn't start rendering anything until both modules arrive. But icons-moderation is only there for the trash-can icon on the delete buttons, and those buttons don't even exist until someone has 2+ Mane or 2+ Beş sections. Right now every user pays that extra module round-trip before seeing the form at all, even though most people won't touch a delete button in the first few seconds. Deferring that one module load until the first trash button is actually built would let the form render sooner on a slow connection, for free.
::::::::::::::::Not really fixable without bigger surgery: data.json's "ziman" (language list) is 75 KB of its 114 KB — the single biggest chunk in the whole payload. But that's needed for the very first field in the wizard (the language picker), so lazy-loading it would just delay the first thing the person needs, not help. The only way to shrink this meaningfully would be turning the language field into a live-search that fetches matches instead of shipping the whole list upfront — a much bigger change, and probably not worth it unless load time is a serious complaint from multiple people.
::::::::::::::::Want me to make the icons-moderation fix? It's small and low-risk — I'd move that module load to fire lazily right before the first delete button is created instead of blocking the initial render. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 16 tebax 2026 (UTC)
:::::::::::::::::Erê :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:14, 16 tebax 2026 (UTC)
::::::::::::::::::-:) Em çend sed ziman hilbijêrin tenê wana nîşan bidin? Niha tenê yên ku lêmayên wan hene tên lîstekirin. nêzîkî 3k ziman
::::::::::::::::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:21, 16 tebax 2026 (UTC)
:::::::::::::::::::Bi min Jimar di tercîhên pêşketî de veşartî be. Zêde nayê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
::::::::::::::::::::"Tewandina lêkerê" rast çêbû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
:::::::::::::::::::::Erê jimar were veşartin. Tewandina lêkerê rind bûye feqet dema bilanî ji bo zazakî ye.
:::::::::::::::::::::Qutiya "Cureyê rêzimanî" pirr nêzîkî nivîsa "Lêker derbasdar e?" ye û dûr nivîsa jor e.
:::::::::::::::::::::Herwiha Gadjetê "Bi otomatîk tijî bike" pirr rind e feqet min bikaranîna wî zû fêm nekir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:25, 16 tebax 2026 (UTC)
::::::::::::::::::::::Min "Çend caran wekî dema bê nayê çêkirin... " fêm nekir. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:34, 16 tebax 2026 (UTC)
:::::::::::::::::::::::"Maneyê bi şablonan çêke (rastnivîs, şasnivîs, aweyekî din)" bi min lazim e pêşiya qutiya mane be, ji ber ku me devok, etîket kategorî zêde kir niha ecêb bûye [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
:::::::::::::::::::::::Bi formên standardê dema fermanî wekî forma sêyem (yekjimar) a dema bê ye (dema bê: ew ê/dê bibêje// fermanî: bibêje). Feqet di nav devokan de ne hertim wisa ye. Mînak bi devoka rojhilatî: ew dê bêt (dema bê) û bibê (dema fermanî). Li vira dema fermanî bi dema bê nayê çêkirin. Feqet ne hewce ye ji ber ku li jêr devok tê xwastin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
::::::::::::::::::::::::An jî dema bê were xwastin, dema fermanî wekî yê standardê tê çêkirin. Min tevlî hev kir :)) . Bi min bes e Gadjetekî tewandinê nîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:49, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::Em bilanî bikin "fermanî" zêde serê xwe neêşînin, wekî din em ê deh qutiyên din çêkin :D Piştî hinek clean-upê jî em biweşînin ji bo niha bes e {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:55, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::Problem, bi rastî, forma bê ye :)) Di çend devokan de pêşgira "bi-" nayê bikaranîn (torî, badînî, ... [[Modul:ku-tewandin#L-298]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::Te tabloya tewandina reşwî dît? [[Bikarhêner:Kurdanparez/sandbox]]. Şanseke me heye ku ew nekirine standard :). Feqet pirr entresan e, formên mê û nêr jî hene. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:23, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::::Qet behsa standardiyê meîne (meyîne?) :D Ji wî alî de gelek xemgîn im. Min ji bo kurmancî forma dema bê tevlî kir, fermanî ji bo zimanên din. Feqat Claude niha naxebite dibêje "Claude is temporarily unavailable" {{=D}} Niha divê çi were paqijkirin. Tiştekî kêm ma?
::::::::::::::::::::::::::::"All the code changes (jimar dropdown from data.json for every language, fdbe → fdbê rename with fdbêtr added, and the "Taybetmendî" heading removed) are done and verified on my end — nothing is lost" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::::Min placeholdera URL, DOI... guhart niha baştir tê fêmkirin înşela? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:07, 16 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::::}}Tiştên ku lazim in were guhartin:
* Placeholdera qismê "Tewandina "kelîme" binivîse"
[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:11, 16 tebax 2026 (UTC)
:Divê ns 108 destek bide. Wextê ns 108 be berîya beşa zimanî {{şx|jinûvesazkirî|langcode}} were zêdekirin, wekî din her tişt eynî ne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 16 tebax 2026 (UTC)
::"Ev mane li ku derê an..." bike "Bi vê maneyê li kû derê an..." [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 16 tebax 2026 (UTC)
:::Ji bo forma bê kesê sêyem were xwastin (ew ê/dê ...) ji ber ku di forma standardê de wekî forma fermanî ye: (tu) ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:22, 16 tebax 2026 (UTC)
:::Kitêb/kovar/websîte lazim e formata fireh kurt wekî [[MediaWiki:Gadget-wikt.add-examples.js]] bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:23, 16 tebax 2026 (UTC)
::::Butonên lînk û înfo ya dropdowna Etîmolojî û Bilêvkirin naxebitin. Ya dropdowna şablonên Maneyê jî du heb hene, yek di nav dropdownê de ku naxebitin ya din piştî hilbijartinê xuya dibin ku ew jî ne hewce ne, tenê lazim e di nav qutiyê de be. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:30, 16 tebax 2026 (UTC)
:::::Di "Tercîhên min" de butona "Hemû cache paqij bike" lazim e cache-yên etîket, dever û kategoriyê jî paqij bike. Niha nake. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:31, 16 tebax 2026 (UTC)
::::::Pill-ên Hevmane/Dijmane etc. lazim in di derveyî qutiya kelîmeyê de xuya bibin piştî nivîsandinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:33, 16 tebax 2026 (UTC)
:::::::"- beşek hilbijêre -" bike "- binebeşên din hilbijêre -" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:34, 16 tebax 2026 (UTC)
::::::::Çavkanî section dropdown linebreak dixe berîya "* şablon" lazim e linebreak piştî navê şablonê were. Îkonên lînk û înfo li vir jî naxebitin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 16 tebax 2026 (UTC)
:::::::::"Binêre herwiha (bnr-h)" bike "Herwiha binêre (h-bnr)"
:::::::::"Şablona bnr-h li qismê herî jor a rûpelê" bike "Şablona h-bnr li qismê herî jor a rûpelê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:36, 16 tebax 2026 (UTC)
::::::::::@[[Bikarhêner:Ghybu|Ghybu]] Bi min problem nema. Fikrê te? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:18, 17 tebax 2026 (UTC)
:::::::::::Temam. Çend tişt 1) Forma dema bê bin forma dema niha. 2) Beşa Tewandinê (ji bo lêkeran) vitira diminî an ji li gor danayên lêkerê (x-gh|reh, ...) tên tijî kirin. 3) Di qutiyên forma dema niha, borî, ... de zû nayê nivîsandin: înterneta te pirr rind e, tu ferq nakî? :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:46, 17 tebax 2026 (UTC)
::::::::::::Te wisa got înterneta min qut bû ew çi îş e :D Înterneta min jî pir hêdî ye feqat min ferq nekiribû. Eynî problem heye. Ji bo lêkeran tewandin bila niha wisa bimîne em dûre rojane bikin, jixwe piştî rastkirina şablonên jêgirtinan ez ê dîsa vegerim vê skrîptê. Niha lazim e bala xwe bidim jêgirtinan. Herwiha em êdî qerarek bidin tewandina lêkeran diçin beşa Tewandinê an beşa rêzimanê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:03, 17 tebax 2026 (UTC)
== [[Mediawiki:Gadget-QQ.js]] ==
Ev amûr di şikle Minerva de. Her daîm vê xeletiyê dide:
<syntaxhighlight lang="js">
TypeError: Cannot read properties of null (reading 'addEventListener') at css () at runScript () at Array.<anonymous> () at flushCssBuffer ()
</syntaxhighlight> [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:29, 18 tîrmeh 2026 (UTC)
:* [[Bikarhêner:Wikihez/skrîpt/js/Gadget-QQ.js]] --> [[Mediawiki:Gadget-QQ.js]]
:* [[Bikarhêner:Wikihez/Gadget-QQ.css]] --> [[Mediawiki:Gadget-QQ.css]]
:Ez dibêjim çêbû lê ka çend roj biceribînim. Tiştên ku hatin sererastkirin:
:# Google Books API Key dikarî zêde bikî, dixe cookies û di gerokê de dimîne. Berê bê API Key dixebitiya lê êdî mecbûr e ku API Key-a te hebe, bidestxistina wê ne zêde zor e lê dîsa jî tevlihev e.
:# Êdî di mobîlê de dixebite, heta di mobîlê de xweştir xuya dike.
:# Moda tarî
:# OOUI dewsa jquery.ui
:# Li qutiyê çi dixwazî zêde bikî, dikare li gorî îsm, sifet, fiîl bitewîne. Lê ji bo fiîl tenê dikare çend heb inflection bîne.
:# Butona paqij bike lê zêde kir.
:# Çend paqijiyên jêgirtinên Google Kitêbê niha hene.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:38, 18 tîrmeh 2026 (UTC)
::Tewandina fiîlan û xeletiya modulê lazim e were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:40, 21 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-wikt.add-examples.js]] ==
Min ev amûr hinek rojane kir feqat çend problem hene berî ku bar bikin.
Tiştên ku min guhart:
# Êdî li sayfeyên bi {{ş|maneid}} jî dixebite,
# Cihê di wîkîtekstê de yê ku jêgirtin were îlawekirin baştir bû,
# [[Mediawiki:Gadget-citoid-helper.js|citoid helper]] zêde kir ji bo ku bi hêsanî çavkanî bi riya doi, isbn an urlyê werin tijîkirin.
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.css]] --> [[MediaWiki:Gadget-wikt.add-examples.css]]
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.js]] --> [[MediaWiki:Gadget-wikt.add-examples.js]]
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 19 tîrmeh 2026 (UTC)
:Min "wikt." qismên cache kirinê ya wîkiya fransî bi [[MediaWiki:Gadget-StorageUtils.js]] guhartiye. loma ji bîr nekin wê amûrê li [[MediaWiki:Gadgets-definition]] zêde bikin. û navê amûrê jî biguhêrînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:15, 23 tîrmeh 2026 (UTC)
::Berê li ser WF:ceribandin dixebitî. niha naxebite. Divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:10, 7 tebax 2026 (UTC)
== Gadget-translation editor ==
* [[MediaWiki:Gadget-translation editor.js]]: divê formata en.wîktê were bikaranîn ji ber ku me gelek tercime ji en.wîktê kopî kir û ev js hîn nikare formata wekî li jêr bi kar bîne.
<syntaxhighlight lang="wikitext">
* {{Z|sh}}:
*: Kirîlî:
*: Latînî
</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Mediawiki:Gadget-linkify.js]] ==
* ''rûpeleke'' wekî<syntaxhighlight lang="wikitext">[[rûpeleke]]</syntaxhighlight> lînk dike, bi regexê belkî dikare wisa bike <syntaxhighlight lang="wikitext">[[rûpel]]eke</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
* Amûreke din wekî [[User:Wikihez/Gadget-linkify.js|linkify]] lazim e ku herfên mezin biçûk bike ji bo kurmancî.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:{{r|Bikarhêner}} Merheba bira, dikarî viya jî bar bikî?:
* [[Special:PermanentLink/6291974|linkify]] --> [[Mediawiki:Gadget-linkify.js]]
Spas--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:42, 29 tîrmeh 2026 (UTC)
:Dûre [https://ku.wiktionary.org/wiki/MediaWiki:Gadget-start.js#L-283 li vir] wiktviLinkify bike kuLinkify xêra xwe [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:51, 29 tîrmeh 2026 (UTC)
::Min ji bîr kir lê zêde bikim niha nikare "Dİ" bike "di", xeletiyeke berbelav a di nivîsên kurdî de, herfên din ên wekî ll yan rr jî hewce ne werin zêdekirin. Dûre em zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:42, 29 tîrmeh 2026 (UTC)
:::Min nû ferq kir, sayfeyên ku hîn nehatine çêkirin wîkîlînk nake. Problemeke mezin e divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:15, 30 tîrmeh 2026 (UTC)
::::Na, lînk dike, tiştekî din heye... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:49, 31 tîrmeh 2026 (UTC)
== Amûra manayê ==
Amûreke din ji bo nivîsina maneyê wekî Nimûne zêde bike lazim e [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6276876 js] [https://ku.wiktionary.org/w/index.php?title=%C5%9Eablon:ceribandin2&oldid=6265629 şablon] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6265642 css] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:12, 23 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-jeder.js]] ==
@[[Bikarhêner:Ghybu|Ghybu]] Çavkaniyên jêderê di moda pêşdîtinê de jêderê vedişêre, ew jî min gelek eciz dike wextê çavkanî zêde dikim. Min ji Claude pirsî û kodan nîşanî wê da, wisa cewab da:
Yes — and this file actually explains the whole thing precisely, better than my earlier guess about extract-stripping.
What's happening: the CSS rule
<syntaxhighlight lang="css">
.jeder{
display:none;
}
</syntaxhighlight>
hides every citation by default, *unconditionally*. The only thing that ever makes it visible again is the gadget's JS: it finds each `.jeder`, builds the short "`— (year, author, +)`" summary from the `.j-pewist` fields, and either reveals the short version or — for citations with no `.j-pewist` fields — explicitly sets `display:inline` on the original.
So visibility of citations depends entirely on this JS running. In the normal article view (Image 1) it runs, so you see the collapsed "`(1918, E. Rehmî Ji Mala Hekariyan, Amadekar: M. E. Bozarslan, +)`" form. In whatever generated Image 2 — a hover-preview/extract/API render — gadget JS doesn't execute, so nothing ever removes the `display:none`, and the citation vanishes completely rather than degrading to "still visible, just not collapsed."
<b>The fix:</b> flip the default so the *un-collapsed* state is the safe fallback, and let JS do the hiding only after it successfully builds the short version. That way, any context without JS shows the full citation instead of nothing.
CSS — remove the blanket hide:
<syntaxhighlight lang="css">
.jeder {
/* intentionally no display:none here — see Gadget-... .js */
}
</syntaxhighlight>
JS — do the hiding from script, after the short form exists:
{{veşêre-ser}}
<syntaxhighlight lang="js">
// Ji bo şablonên [[Şablon:jêder]] hatiye nivîsandin.
// Parametreyên ku di navbera "<span class="j-pewist">...</span>" an "{{j-pêwîst|...}}" de ne venaşêre.
$(function() {
function nivisaKurtTevliBike(sund, nivisKurt) {
var zede = $('<a>+</a>')
.addClass('click')
.attr('href', '#')
.attr('title', 'zêdetir')
.css('font-weight', 'bold');
var kurt = $(' <a>↩</a>')
.addClass('click-kurt')
.attr('href', '#')
.attr('title', 'veşêre')
.css('font-weight', 'bold');
$('<span>' + nivisKurt + '</span>')
.append(zede).append(')')
.addClass('jeder-kurt')
.css('font-size', '0.85em')
.insertAfter(sund);
sund.append(' ').append(kurt);
// Only hide the full citation once the short summary actually exists.
// If this script never runs (no-JS clients, hover-preview extracts,
// action=render, etc.), the full citation stays visible instead of
// silently disappearing.
sund.css('display', 'none');
}
function vesere() {
$('.jeder').each(function() {
var elmTab = $(this).find('.j-pewist').clone();
if (elmTab.length) {
var elm = '';
for (var i = elmTab.length - 1; i >= 0; i--) {
elm = elmTab.eq(i).html() + ', ' + elm;
}
var nivisKurt = ' — (' + elm;
nivisaKurtTevliBike($(this), nivisKurt);
}
// else: no j-pewist fields to shorten — leave the full citation
// as it already renders (visible), nothing to do.
});
}
vesere();
$(".click").click(function(e) {
e.preventDefault();
$(this).parent('.jeder-kurt').css('display', 'none');
$(this).parent('.jeder-kurt').prev().css('display', 'inline');
});
$(".click-kurt").click(function(e) {
e.preventDefault();
$(this).parent('.jeder').css('display', 'none');
$(this).parent('.jeder').next().css('display', 'inline');
});
});
</syntaxhighlight>
Net effect:
- JS-enabled readers (normal article view): identical experience to today — collapsed short form with `+`/`↩` toggle.
- No-JS contexts (previews, extracts, screen readers before JS loads, print, etc.): full citation is shown instead of nothing — a real regression fix rather than a cosmetic one.
One caveat worth being upfront about: this fixes it *if* the preview context renders the gadget's HTML/CSS but doesn't execute gadget JS (which is the far more common case, and what this code strongly suggests is happening). If Image 2 instead comes from a pipeline that strips the `.jeder`/`.jegirtin-text` elements out of the HTML entirely before this CSS/JS even gets a chance to apply, no on-wiki CSS/JS change can help — that'd need the extract-generation config changed instead. Given what this gadget file shows, though, I'd bet on the CSS-hide explanation.
{{veşêre-bin}}
Em dûre biceribînin eger xeletî tinebe, vî tiştî jî rast bikin. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:00, 23 tîrmeh 2026 (UTC)
:Ez APIya MediaWiki rind nizanim belkî Claude dizane:) Em nikarin bibêjin dema ku moda "''action=edit''" e fonksiyona <code>function vesere()</code> bi kar meyîne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:57, 24 tîrmeh 2026 (UTC)
::Na, di moda "edît"-ê de <code>var elmTab = null</code>... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:13, 24 tîrmeh 2026 (UTC)
?:
<syntaxhighlight lang="js">
if (
mw.config.get('wgAction') === 'edit' ||
mw.config.get('wgAction') === 'submit'
){
var elmTab = [];
} else {
var elmTab = $(this).find('.j-pewist').clone();
}
</syntaxhighlight>
--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:31, 24 tîrmeh 2026 (UTC)
:Ez jî nizanim Claude çiqas ji mediawiki fêm dike lê heta niha ti tişt nîne ku min jê pirs kir û nikaribû çêke, bila wekî reklam nebe feqat Claude mikemmel e, dibêjim "li vir çima xeletiyeke wisa derdikeve" di dosyeya 2000 rêzî de diçe tiştê xelet tenê rast dike tiştekî din naguherîne. Chatgpt û aistudio qet wisa nakin, eynî dosyeyê deh cara dinivîsin, deh netîceyên cuda derdikevin :D
:Em dikarin vê amûrê wextê edit û submitê neaktîv bikin wekî <syntaxhighlight lang="js">if (!(action === "edit" || action === "submit")) {
:return;</syntaxhighlight> lê wê demê display:none wê çibe nizanim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:23, 24 tîrmeh 2026 (UTC)
::display:none ne hewce ye ma jixwe di nav kodê [https://ku.wiktionary.org/w/index.php?title=MediaWiki:Gadget-jeder.js&oldid=6069025#L-49 L-49] de inline/none heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:53, 24 tîrmeh 2026 (UTC)
:Min bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.js&diff=prev&oldid=6290816#L-28] ceriband feqat nexebitî. "display:none" di pêşdîtinê de dixebite feqat js naşixule. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:43, 25 tîrmeh 2026 (UTC)
::Kodên JSê pir hez nakim (pêşta hukurina APIya MWyê hewce bû) dîsa jî ez ê seh bikim :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:49, 27 tîrmeh 2026 (UTC)
:::Ez niha bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6290820] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6290821#L-8] bi kar tînim ti problem nîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:09, 27 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-PagePreviews.js]] ==
Ez dibêjim em vê amûrê jî çêkin [[:en:MediaWiki:Gadget-PagePreviews.js]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:56, 3 tebax 2026 (UTC)
= Moda tarî =
== [[Şablon:wîkîpediya]] ==
Rengê tarî de xirab e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:36, 4 tebax 2026 (UTC)
= Bot =
Karên ku bi botê werin çêkirin ji kerema xwe li vir binivîsin.
== Kategorî:Biwêj ==
[[:Kategorî:Biwêj]] divê were paqijkirin, lîsteya madeyên {{ş|biwêj}} çêke, yên ku tê de bûn, kirin, man hene filtre bike yên din bi destî tê çêkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Jêgirtin ==
Xistina jêgirtinên beşa [https://ku.wiktionary.org/w/index.php?search=insource%3A%2F%3D+*Ji+%5BWw%5D%C3%AAjey%C3%AA+*%3D%2F&title=Taybet%3AL%C3%AAger%C3%AEn&profile=advanced&fulltext=1&ns0=1&searchToken=8rh0bmw8dccsi7aynl6zli8uf ji wêjeyê] li beşa maneyê. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Bota Toolforgeê ==
Botekî Toolforgeê lazim e ji bo ku xeletiyên biçûk wekî şablonên bi navên îngilîzî werin kurdîkirin yan jî valahiya beşan rast bike û hwd.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:Min [[Bikarhêner:Wikihez/skrîpt/py/kuwiktkozmetik.py|paqijiyen kozmetîk]] hazir kir feqat hîn navên şablonan naguhêre. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 4 tebax 2026 (UTC)
== Updatew ==
Bota [[Bikarhêner:Wikihez/skrîpt/py/updateW.py|updateW]] divê were rojanekirin. Yan jî yekî nû were çêkirin. Şablonên {{ş|W}} di beşa tercimeyan de sererast dike û zimanan rêz dike. Ya niha xeletiya navê zimanî dide.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== sihir --> sêhr ==
Tercimeyên ''[[sêhr]]'' hemû dibêje ''sihir'' loma bi botê lazim e kirin ''sêhr''. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:43, 18 tîrmeh 2026 (UTC)
:Sihirbaz --> sêhrbaz [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 18 tîrmeh 2026 (UTC)
== Tercimeyên dîroknas ==
Li tercimeyên [[dîroknas]], hevmaneyên wê hewce ye were îlawekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:44, 18 tîrmeh 2026 (UTC)
== Mewlûd ==
Sererastkirina jêgirtinên mewlûdê [https://ku.wiktionary.org/w/index.php?search=insource%3A%2Fw%5C%3AMela+His%C3%AAn%C3%AA+Batey%C3%AE%2F&title=Taybet%3AL%C3%AAger%C3%AEn&ns0=1&searchToken=8m0tguy3sqt7edr4sg0u8hedo] wekî [https://ku.wiktionary.org/w/index.php?title=dax%C3%AEdar&curid=284303&diff=6265851&oldid=6142285] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:43, 20 tîrmeh 2026 (UTC)
== [[Şablon:jêder]] ==
Şablon jêder axlebî bi şiklê MM/YYYY sal tê de heye, bi botê em dikarin wana bixin {{para|sal}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:07, 17 tebax 2026 (UTC)
dtfk2oz3ke2lbzgms8l8wol6j5o0fb6
6334376
6334374
2026-08-17T18:25:09Z
Wikihez
7344
/* MediaWiki:Gadget-start.js */ Bersiv
6334376
wikitext
text/x-wiki
{{/ser}}
= Karên ferhengî =
== Mane ==
* [[:Kategorî:Madeyên bê beşa mane]]
** [[:Kategorî:Madeyên bê beşa mane bi kurmancî]]
Li ser van rûpelan ev made hene ku maneyan wan nehatine nivîsin û divê werin nivîsin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Etîmolojî ==
*[[:Kategorî:Daxwazên etîmolojiyê]]
**[[:Kategorî:Daxwazên etîmolojiyê bi kurmancî]]
Li ser van rûpelan ev made hene ku etîmolojiya wan hîn nehatine nivîsin. Ji bo alîkariyê binêre [[WF:Etîmolojî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Cureyê rêzimanî ==
* [[:Kategorî:Gotarên bê kategoriya rêzimanî]]
* [[:Kategorî:Gotarên bê kategoriya rêzimanî bi kurmancî]]
Li ser van rûpelan ev made hene ku cureyên wan yên rêzimanî hîn nehatine nivîsin. Ji bo alîkariyê binêre [[Alîkarî:Cureyên peyvan]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Kontrol ==
* [[:Kategorî:Kontrol]]
** [[:Kategorî:Kontrola maneyê]]
Ev madde bi botê hatine çêkirin, yek bi yek divê werin kontrolkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
= Şablon =
== IPA ==
* {{ş|IPA}} divê [[Modul:IPA]] bi kar bîne. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Deng ==
Kurdîkirina belgekirina {{ş|deng}} lazim e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 19 tîrmeh 2026 (UTC)
= Modul =
== Modul:form of/templates ==
* [[Modul:form of/templates]]: Divê ev modul were rojanekirin û xeletiyên berê wekî ([[hevalê]]) werin rastkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:Merheba @[[Bikarhêner:Ghybu|Ghybu]] Te gotibû di vê modulê de xeletî hene, jkx tu dikarî li vir binivîsî çi kêmasî hene, daku wextê rojane dikim ez ji bîr nekim? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:16, 21 tîrmeh 2026 (UTC)
*::Silav. Nizanim ji bîra min çûye... Herwiha ji bo çêkirina formên navdêran zanînên cîns û tîpa dawî bes e:
*::* (-ê, mê) => çemandî, bangkirin
*::* (-ê, nêr) => îzafe
*::* (-ê, mê û nêr) => îzafe (nêr), bangkirin (mê), çemandî (mê)
*::* (-a) => îzafe (mê)
*::* (-ekî) => îzafe û çemandî (nêr, nebinavkirî)
*::* (-eke) = > îzafe (mê, nebinavkirî)
*::* ...
*:: <code>if find(mw.title.getCurrentTitle().subpageText, 'ekî$') then ... end</code>
*::<code><nowiki>{{ku-tewîn-navForm|<navdêr>|<zayend>}</nowiki></code>
*::Tablo û nivîs di yekcarî de tên çêkirin...
*::Feqet problem lêzêdekirina mînakan e. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:34, 21 tîrmeh 2026 (UTC)
*:::Lêzêdekirina kîjan nimûneyan? Ev koda js ji bo çi ye? Qisûrê nenêre min fêm nekir :/ [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:13, 21 tîrmeh 2026 (UTC)
*::::Li vira ([[hevalê#Formeke navdêrê]]) di bin maneyên 1. û 2. mînak nayên tevlî kirin :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 21 tîrmeh 2026 (UTC)
*:::::Ev tercîh e, ne mecbûrî ye, em dikarin [https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&diff=prev&oldid=6276350 wisa] bikin. Wekî din? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:29, 21 tîrmeh 2026 (UTC)
*::::::Min li jor tênê got ku tenê parametreyên "navdêr" û "zayend" hewce nee. Bi '''[//ku.wiktionary.org/w/index.php?title=heval%C3%AA&curid=969734&diff=6276353&oldid=6276350 vî]''' awayî jî tê çêkirin. Parametreyên din ne hewce ne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:38, 21 tîrmeh 2026 (UTC)
*:::::::Ji bo wê şabloneke din lazim e, bila ev şablon wekî îngilîzî bimîne em ji bo kurmancî yekî din çêkin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:42, 21 tîrmeh 2026 (UTC)
*::::::::[https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&oldid=6276885 wisa] çawa ye? Bi min nivîsîna nimûneyan zêde ne lazim e ji ber ku ti kes serê xwe pê naêşîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:46, 23 tîrmeh 2026 (UTC)
*:::::::::@[[Bikarhêner:Ghybu|Ghybu]] Vê şablonê bi kar bînin an na? Navê şablonê kê çi be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:07, 24 tîrmeh 2026 (UTC)
== Modul:columns ==
* [[Modul:columns]] were rojanekirin û {{ş|kol3}} bila lînka ''nîşan bide'' deyne beriya sernavê, kolê di qutiya rengî de bila nebe. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:etymon ==
* [[Modul:etymon kevn]]: Rojanekirin hewce ye. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Modul:ziman/code to canonical name]] ==
Belgekirina skrîptê û js divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:31, 22 tîrmeh 2026 (UTC)
:* [[MediaWiki:UpdateLanguageNameAndCode.js]]
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:50, 22 tîrmeh 2026 (UTC)
::* [[Modul:ziman/print]]
::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:52, 22 tîrmeh 2026 (UTC)
:::*[[Modul:ziman/şablon]]
:::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:56, 22 tîrmeh 2026 (UTC)
::::* [[MediaWiki:Gadget-translation editor.js/ziman.json]]
::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:06, 22 tîrmeh 2026 (UTC)
== [[Modul:quote]] ==
Piştî ev modul hate hazirkirin, lazim e navên jegirtinan werin rastkirin û li hemûyan {{param|jêgirtin}} were zêdekirin. Bnr. [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Mem û Zîn]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 5 tebax 2026 (UTC)
::Ev modul hema bêje hazir e. Hûn li ser van madeyan çi difikirin:
# {{para|tarîx}} lazim e full date be eger na, lazim e {{para|sal}} were bikaranîn. Feqat "'''2021''' kanûna pêşîn 23" bi vî şiklî bi min ne problem e çnkî derdê en.wikt "attestation" e delîla herî pêşî jî sal e.
#: {{demo|sep=wisa xuya dibe<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|tarîx=23 December 2021|nivîskar=Harry Hobbs; George Williams|sernav=Micronations and the Search for Sovereignty|url=https://www.cambridge.org/core/books/micronations-and-the-search-for-sovereignty/B0414A3A322695D9EE71E4CDDFDA06DE|weşanxane=Cambridge University Press|rûpel=155|isbn=978-1-009-15012-5|jêgirtin=jêgirtin.|t=-}}</nowiki>}}
#{{para|amadekar}}/{{para|edîtor}}/{{para|terciman}} bi şiklê edîtoriya, amadekariya, tercimekirina
#: {{demo|sep=dide<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|sernav=Medical Surgical Nursing|serî=Juta's Manual of Nursing|cildê-seriyê=volume 4|edîtor=Sophie Mataniele Mogotlane; Joyce Mokoena; Motshedisi Eunice Chauke; Anne Young|sal=2005|weşanxane=Juta Academic|isbn=978-0702171840|rûpel=!12-29|jêgirtin=jêgirtin|t=-}}</nowiki>}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:09, 10 tebax 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Hîn gelek tişt hene divê îzah bikin û fikrê we pirs bikim, feqat îzahkirin pir zor e, em dest bi bikaranînê bikin û dûre tiştên ku hûn naxwazin em dikarin biguherînîn, dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:11, 10 tebax 2026 (UTC)
::Dibe, piştî çêbû, em di praktîkê de bibînin ku çawa ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:05, 10 tebax 2026 (UTC)
= Javascript =
== [[Mediawiki:Gadget-citoid-helper.js]] ==
Ji bo bi citoidê kopîkirina malûmatên çavkaniyan amûreke wisa lazim e. Xisûsen ji ber ku bi çend amûrên din dikare were bikaranîn.
Feqat çend problem hene, şablonên me de kêmasiya parametreyan gelek in û xerîteyên citoidê hîn nehatine çêkirin. Wana sererast bikin dûre vê amûrê çêkin, piştre [[MediaWiki:Gadget-start.js|start.js]] û [[MediaWiki:Gadget-wikt.add-examples.js|add examples.js]] were rojanekirin.
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:04, 19 tîrmeh 2026 (UTC)
:Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]], ez dibêjim karê Modul:quote zêde wext bistîne, bila em dest bi bikaranîna van amûran bikin dûre kêmasiyan em ê temam bikin înşela. Dikarî vana li cihên xwe yên rast kopî paste bikî?
# [[Special:PermanentLink/6291574|Bikarhêner:Wikihez/skrîpt/js/Gadget-citoid-helper.js]] --> [[Mediawiki:Gadget-citoid-helper.js]]
# Ya li jêr kopî bike qismê herî dawî ya AmûrênVeşartî li [[MediaWiki:Gadgets-definition]]:
## <code><nowiki>* citoid-helper[ResourceLoader|hidden|default|dependencies=mediawiki.util]|citoid-helper.js</nowiki></code>
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:58, 26 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-start.js]] ==
Rojanekirin hewce ye. Min gelek tişt lê zêde kir lê ez niha test dikime. Piştre em bar bikin:
* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 17 tîrmeh 2026 (UTC)
:Min [[MediaWiki:Gadget-start/data.json|data.json]] çêkir ji bo ku bi hêsanî malûmatên heyî werin guhartin bêyî guhartina Javascriptê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:49, 18 tîrmeh 2026 (UTC)
:Xeletiyên ku ji bîr nekim rast bikim:
:* Wextê min çavkanî "kitêb" hilbijart û bi destî tije kir xeletiyeke [https://ku.wiktionary.org/w/index.php?title=xezete&diff=prev&oldid=6265392 wisa] derket her kelîme hate lînkkirin.
:* <code>html("⠿")</code> ev ne lazim e.
:* textarea ji bo çavkaniyên jêgirtinan xaseten yên sernav hîn jî ne draggable e.
:* pêşdîtina zindî checkbox wextê tê rakirin, pêşdîtin winda nabe.
:* Butona mobîlê ya vekirina formê di moda tarî de belî nabe, tenê wekî qutiyeke tarî xuya dibe.
:* Metna hişyarkirinê di guhartina rûpelên heyî de lazim e baştir bikim.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:51, 18 tîrmeh 2026 (UTC)
::Ev problem çêbûn, ez ê binêrim ka çi problemên din hene. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:15, 19 tîrmeh 2026 (UTC)
:::Çêke: Ji bo qismê çavkaniyê yek dikare API call û Kategorî:Çavkanî bi kar bîne ji bo ku şablon zêde bikî ji yên heyî. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 19 tîrmeh 2026 (UTC)
::::Ev jî çêbû, min beşa çavkanî jî çêkir. Eger xeletî dernekeve û problema valahiya piştî etîketê ji bîr nekim, piştî rojanekirina amûrên linkify, û citoid em dikarin bar bikin. Ez ê ti tiştê nû îlawe nekim, zêde dirêj bû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:14, 21 tîrmeh 2026 (UTC)
:::::Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]]. Dikarî wana bar bikî?
:::::* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
:::::* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
:::::* [[MediaWiki:Gadgets-definition]] wisa rojane bike:
:::::* start[ResourceLoader|default|package|dependencies=mediawiki.util,ext.gadget.StorageUtils,ext.gadget.citoid-helper]|start.js|start.css|start.js/data.json
:::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:39, 5 tebax 2026 (UTC)
{{şirove}} Dewama ''[[Special:PermanentLink/6333688|Gotûbêja bikarhêner:Rewiye tine#WF:Nû]]'' bila gotûbêja wî em spam nekin. :D
{{r|Ghybu}} dikarî [[Special:PermanentLink/6333689|vê htmlê]] binêrî claude rast fêm kiriye yan na? Daxîne û wek html qeyd bike. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:43, 14 tebax 2026 (UTC)
:Min seh wîkîyên din jî kir, li wîkiya fransî tiştekî wekî daxwaza min çêkirine (tiştên ku ne hewce ne wekî "tab" çêkirine an jî hatine vêşartin): [//fr.wiktionary.org/w/index.php?title=fdsdfdsf&action=edit&redlink=1]. Formata me yê berî jî bi vê lojîkê hatibû çêkirin, wekî de.wiktionary ([//de.wiktionary.org/w/index.php?title=sdfdsfsdfs&action=edit&preload=Vorlage:Formatvorlage_(Substantiv_vereinfacht)]) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:01, 14 tebax 2026 (UTC)
::PS: Dîsa jî Claude min rind fêm kiriye :)) Feqet min got ku beşên Mane", "Bilêvkirin" û "Beşên din" cem hev (wekî "tab") ". Û dema ku tu terî di nav "Besên din" Hevmane, Jê, ... li wira werin dayîn. Wêne/Resim û Nimûne jî di nav beşa ("tab") "Mane" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:17, 14 tebax 2026 (UTC)
::Erê ya fr.wikt min jî dîtibû jixwe fikrê tab min ji wan girtibû :D (feqat min li cem wan tenê tab-a maneyê dît yên din nedît çawa tê vekirin nizanim) Berê hemû beş piştî maneyê bibûn, dûre min sê Tab çêkir, Bingehîn+Lînkkirin+Bikaranîn. Niha lazim e Tabeke din çêkim. Ya ku claude çêkiriye bnr. [[Special:PermanentLink/6333698#L-7]] ne baş e bnr. [[Special:PermanentLink/6333703|qiyaskirinê li ser vê htmlê]] Êdî ez Claude abuse dikime ji ber ku Pro ye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:26, 14 tebax 2026 (UTC)
:::Claude dibêje:
:::Good clarification — that's a real simplification, not just a relabel. Updated STEP_GROUPS from 5 tabs down to 3:
:::Mane — the definitions themselves, plus Wêne (image), now grouped in with it instead of sitting in a separate misc tab. Nimûne needed no change since it was already rendering inside each Mane sense block, not as its own step.
:::Bilêvkirin — unchanged, still just pronunciation.
:::Beşên din — everything else in one flat bucket: hevmane, jê, têkildar, herwiha binêre, formên alternatîv, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, tewandin, etîmolojî, zaravayên din, werger, and bnr-h — instead of being split across the old Bingehîn/Lînkkirin/Bikaranîn/Din tabs.
:::No DOM-order changes were needed this time — the fix from the blank-tab bug looks up the tab bar's position dynamically now, so it didn't matter that I regrouped which steps go where. Same two files, saved to your Wiki folder — worth another pass through all three tabs once you've pasted this in, same as last time. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:34, 14 tebax 2026 (UTC)
::::Mînak:
Ziman
---------------------------------------------
Cureyê rêzimanî (Navdêr)
Navdêr (hilbijêre) ┆ Cîns (hilbijêre) ┆ Jimar (hilbijêre)
- lêker -mê -yekjimar
- rengdêr -nêr -pirjimar
- ... -... -...
---------------------------------------------
Mane || Bilêvkirin || Beşên din
hilbijêre:
- mane - IPA - Hevmane
- nimûne - kîte - Dijmane
- wêne
Dema ku tu "Cureyê rêzimanî" dikî "Lêker" :
Cureyê rêzimanî (Lêker)
Lêker (hilbijêre) ┆ Cure (hilbijêre)
- navdêr -rehê niha
- rengdêr -rehê borî
- ... -...
---------------------------------------------
┆ : select/option ("tab" nîne, tê dîtin)<br>
|| : "tab"
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:45, 14 tebax 2026 (UTC)
'''Sê-stûnî ("┆" = ne tab), li gorî Cureyê rêzimanî:'''
{| class="wikitable"
! Cureyê rêzimanî !! Stûna 2 !! Stûna 3 !! Têbînî
|-
| Navdêr / Serenav
| Cîns (hilbijêre ▾: mê / nêr / ... — tenê heke ziman zayendê rêzimanî hebe)
| '''Jimar''' (?) — pirs li jêr
| Qadên "Forma mê/nêr/pirjimar/yekjimar" niha jî li vir hene — ma ev di "Jimar"-ê de tên tevlihev kirin, an cuda dimînin?
|-
| Rengdêr
| (heman m/n/p/y forman ku Navdêr distîne)
| Sûperlatîv (sûp) / Komparatîv (komp)
| Ne li ser her du nîgaran hatiye behs kirin — ma sê-stûnî li vir jî tê bikaranîn?
|-
| Lêker
| '''Cure''' = Cureyê lêkerê (hilbijêre ▾: gerguhêz / negerguhêz / xwerû-... / pêkhatî-... / hevedudanî-... / biwêjî-... — 10 vebijêrk)
| Di heman stûnê de: Rehê dema niha / Rehê dema borî / Rehê dema bilanî (her sê qadên nivîsandinê ne, ne hilbijêrk in)
| Ma "Cure" her sê rehan di xwe de digire wek yek stûn, an her yek stûneke cuda ye?
|-
| Tîp (herf/sembol)
| Girdêk (gir) / Hûrek (hûr)
| —
| Ne behs kiriye
|-
| Her cureyek, li gorî zimên
| Transliterasyon (tr) / Transkrîpsiyon (ts)
| —
| Bi zimên ve girêdayî ye, ne bi cureyê rêzimanî ve
|-
| Her cureyek, tenê ziman=japonî
| Hanja / Translit. hanja
| —
| Ne bi cureyê rêzimanî ve girêdayî ye
|-
| Her cureyek
| '''Tercîhên pêşketî''' (kolapsî): form, formaalt, nekatcure, nekatcins, nekatçendterm, serlînkneke, nekatpalîndrom, neformtr
| —
| Vebijarkî, ji bo hemû cureyan
|}
'''Tabên jêrîn — Mane ‖ Bilêvkirin ‖ Beşên din ("||" = tab):'''
{| class="wikitable"
! Mane !! Bilêvkirin !! Beşên din (hemû 18 hêman)
|-
| mane, nimûne, wêne
| şablonên bilêvkirinê (mînak: IPA, kîte)
| tewandin, etîmolojî, bi zaravayên din, werger, jê, têkildar, herwiha binêre, formên alternatîv, hevmane, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, bnr-h
|}
'''Pirsên vekirî:'''
# "Jimar" (yekjimar/pirjimar) — qadeke ku nîşan dide têketî bi xwe yekjimar e an pirjimar e. Ev cuda ye ji "Forma yekjimar (y)"/"Forma pirjimar (p)" ya heyî (ku forma DÎN a kelîmeyê tomar dike). Herdu bimînin, an werin yek kirin?
# Ma sê-stûnî tenê ji bo Navdêr/Lêker e, an ji bo Rengdêr/Tîp jî heman mantiq derbas dibe?
# Di "Beşên din" de: dixwazî pêşî navnîşek/hilbijartinek nîşan bide (17 hêmanên din jî tê de), û qad tenê piştî hilbijartinê xuya bibin?
:Tu dikarî start.js ji Tercîh/ Amûran rakî û bi test.js li Amûran herî taliyê ye biceribînî. Claude ya li jor hazir kir, Jimar ez dikarim îzah bikim. Feqat bi min di tab-a maneyê de lazim e Bi zaravayên din (heta wek werger her car bila li rûpelan were zêdekirin) û werger jî hebe.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:18, 14 tebax 2026 (UTC)
:;Navdêr: Tenê "cîns" û "jimar" bes e. "Forma mê", "Forma nêr", ... nayê fêm kirin. Bi min wekî "Tercîhên pêşketî" bi navkî din veşartî çêke.
:;Lêker: "Cureyê lêkerê" nayê fêm kirin, bi min pêşta "geguhez/negerguhêz" were hilbijartin, paşê jî form "hevdudanî/pêkatî/..." ji ber ku her kes vana nizanî... Dîsa jî tu dizanî.
:Bi min beşên "Hevmane" û dijmane jî bike de nav beşa Mane ("tab") ji ber ku pirr tên xwestin.
:Şûnda Wêne jî Nimûne/jêgirtin...
:Herwiha hemû bi rengê şîn hatine çêkirin, nizanim beşên din (===) bi rengê gewr an reş çito dibe? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:35, 14 tebax 2026 (UTC)
::Nimûne/jêgirtin, hevmane, Dijmane li binê her maneyê hene. ji ber ku wana bi maneyê ve girêdayî ne lazim e wisa bimîne feqat belkî şiklekî baştir bibînin. Hînbûna bikaranîna formeke nû bi qasî hînbûna koda wîkiyê zor e heqîqeten... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 14 tebax 2026 (UTC)
:::Min şaş fêm meke feqet te amûrek ji bo Wikihez çêkiriye :) Tu dixwazî ku hemû tiştên ku te çêkiriye tevlî vê Gadgetê bikî û bikarhênerên din jî bi kar bînin... Tu jî dizanî ku kesên ku tên vira çito peyv çêdikin: Ziman > Mane > Mînak > Hevmane > Dijmane ! Du Gadget çêke yek ji bo bikarhênerên ku hertim li vira ne, yekeke din jî ji bo bikarhênerên din (yên ku pirr nên vira).
:::Herwiha kesên ku hertim li vira ne bi Gadgetê gotar çênekin, ''copy/paste'' dikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 14 tebax 2026 (UTC)
::::Min xwest her tişt li ser rêza xwe be ji bo ku hîn bibin çi diçe kû derê feqat bi vî şiklî jî zêde dirêj û tevlihev dibe. Loma ez ê wekî tu dibêjî bikim her tiştê din bila collapsed pêşketî be. Ê ku dixwaze pê lê ke û binêre çi dikare were zêdekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 14 tebax 2026 (UTC)
:::::Niha baştir tê fêm kirin {{=)}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:01, 14 tebax 2026 (UTC)
::::::Min ji Opus 5 û Design Tool îstifade kiriye hema bêje milyonek token çûn :D pêşî dîzayn kir dûre dest pê kir wekî dîna bişuxile. Pereyê xwe %100 heq dike welleh :D feqat moda tarî ji bîr kiriye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:12, 14 tebax 2026 (UTC)
:::::::Spas! Etîmolojî jî wekî "Bilêvkirin", di beşekê din de cem wê, were çêkirin (Mane|Etîmolojî|Bilêvkirin|Beşên din)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:20, 14 tebax 2026 (UTC)
::::::::(Mane>Tewandin>Werger|Etîmolojî>Dûnde>|Bilêvkirin(formatek çawa?)>Resim|Eleqedar>Jê>Têkildar>Herwiha binêre|Beşên din)? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:29, 14 tebax 2026 (UTC)
:::::::::Nizanim, biceribîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:43, 14 tebax 2026 (UTC)
::::::::::"Li şablonekê bigere" ser Maneyê hatiye nivîsandin, nizanim rind tê fêm kirin an na? Min paşê fêm kir :)) Naskirina Wîkîferhengê hewce ye... Li bin were nivîsandin û "notek" hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:02, 15 tebax 2026 (UTC)
:::::::::::"Malûmatên pêşketî nîşan bide" jî bike ser "Hevman" ji ber ku pirr li jor e. Tu nizanî çi dike (nayê dîtin)... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:09, 15 tebax 2026 (UTC)
::::::::::::Dever/Devok jî were nivîsandin ? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 15 tebax 2026 (UTC)
:::::::::::::Tistekî din, dema ku tu maneyê dinivîsî bi otomatîk li jêr mane "2" tê nivîsandin, ew jî ne baş e (pirranî ne hewce ye). Bi min wekî "tab" cem "Mane 1" were vekirin (MAne1|Mane 2|Mane 3), bi otomatîk û an jî bi tikandina ser "Maneyeke din lê zêde bike" (cem beşa Maneyê). An jî bin beşa Wergerê bi nivîsa "maneyeke din lê zêde bike" were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:44, 15 tebax 2026 (UTC)
::::::::::::::Ji bo mane2, mane3, ... problemek heye: "Beşên din" ji bo kîjan maneyê ye :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:49, 15 tebax 2026 (UTC)
:::::::::::::::Devok/dever ya rastî ji bo kurmancî herî muhîm e lê diçe qismê etîketê ew jî veşartî ye, Niha çawa çêbûye? Min butona +mane zêde bike îlawe kiriye. Beşên din ji bo hemû maneyan e, ji bo maneyên xisûsî lazim e yek qismê malûmatên zêdetir bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:01, 15 tebax 2026 (UTC)
::::::::::::::::mane1, man2, ... cem beşa "Mane" çito ye, wekî:
::::::::::::::::<poem>
:::::::::::::::: '''Mane''' || Etîmoljî || Eleqedar || Beşên din
:::::::::::::::: -----------------------------------------------------------------
:::::::::::::::: Mane 1 || '''Mane 2''' || + Mane zêde bike [[Wêne:OOjs UI icon trash-destructive.svg|18px]]
:::::::::::::::: <small>Maneya kelîmeyê binivîse. Mane otomatîk tê ...</small>
::::::::::::::::┌───────────────────────────────────┐
::::::::::::::::│ Kelîmeyên ji bo... │
::::::::::::::::└───────────────────────────────────┘
:::::::::::::::: -----------------------------------------------------------------
::::::::::::::::</poem>
::::::::::::::::Belkî çêkirin zor e... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:59, 15 tebax 2026 (UTC)
:::::::::::::::::Ev nimûne çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:05, 15 tebax 2026 (UTC)
::::::::::::::::::Te beşa "Bilêvkirin" kiriye cem "Beş1", erê. Li gor vê lojîkê "Etîmlojî" jî were cem beşa "Bilêvkirin", na? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:19, 15 tebax 2026 (UTC)
::::::::::::::::::Etîmolojî h4 e Bilêvkirin h3 loma min Bilêvkirin xiste cem "Beş1". Tu dibêjî îzaha qismê Maneyê "Maneya kelîmeyê binivîse. Mane otomatîk tê" li bin tabên Maneyê bin ne wisa? Pir ferq dike? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:23, 15 tebax 2026 (UTC)
:::::::::::::::::::Checkbox ji bo Etîmolojiya "Beş2" zêde bikim? Eger truthy be bila Etîmolojî h3 be û here pêşiya crz (cureyê rêzimanî em jî hinek kurtkirin bi kar bînin êdî) ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 15 tebax 2026 (UTC)
:::::::::::::::::::Na min tiştekî witira negot, min tenê ji bo fêmkirinê ew şemayê çêkir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:27, 15 tebax 2026 (UTC)
::::::::::::::::::::OK, niha rind bû :) "Maneyeke din zêde bike" çito ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 15 tebax 2026 (UTC)
::::::::::::::::::::Temam wê demê her tişt çêbû... Însan hîn dikare gelek tişt zêde bike, feqat usage limits hene :D Min session-a 5 saeatî temam kir. Dûre ez ê wisa bikim wextê pê li taba Mane-Etîmolojî dikî here jor, bi vî aawayî piştî dagirtina her tiştî li jêr însan dikare bi hêsanî maneyên din binivîse. Pişhingî jî ez ê paqiya zimanî bikim, piştî hinek ceribandinan em nû bikin. Eger fikrên nû were dûre dîsa dikarin îlawe bikin :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:35, 15 tebax 2026 (UTC)
:::::::::::::::::::::Temam! Dema ku tu "Mane 2" an "Mane 3" bi şaşî nivîsand... çito tê/n jêbirin? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 15 tebax 2026 (UTC)
::::::::::::::::::::::Vala bikî bes e, beşên vala dernakevin li outputê. butonek çêkin? li kû derê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:49, 15 tebax 2026 (UTC)
::::::::::::::::::::::Cem "Maneyeke din zêde bike" '''jêbibe''' were nivîsandin an jî wêneyê [[Wêne:OOjs UI icon trash-destructive.svg|22px]]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:51, 15 tebax 2026 (UTC)
:::::::::::::::::::::::Mane 1 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] Mane 2 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] +Maneyeke din zêde bike çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 15 tebax 2026 (UTC)
::::::::::::::::::::::::Nizanim kîjan nîşan baş e: va jî heye: <code>✕</code> [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:03, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::Bi min tenê yek carê were nivîsandin ji ber ku tu bi şasî maneyên xwe jî jê dibirî (dema ku tu li ser maneyê ditikînî) :) Li cem "Beş zêde bike" jî were nivîsandin [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::Bi kurdmancî "Rehê dema bilanî (subj)" ne hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:48, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::::Lazim e pêşî em şablonan li gorî zimanan çêkin wekî en.wikt {{şx|en-lêker}} dûre li gorî parametreyên TemplateDatayê em qutiyan dikarin zêde an kêm bikin. Niha Malûmatên şablonên etîmolojî, bilêbkirin û guharto hhwd. ji templatedayê dîstinê wextê pê li butona (i) dikî, lê gelek ji wan ne xwedî TemplateData ne. Hîn karên din jî hene divê em bikin wekî sererastkirina şablonên jêgirtinê. Loma niha wisa bimîne. Em li gotûbêja amûrê beşa Daxwaz çêkin? tiştên ku tên bîra me em li wir binivîsin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::::Min yên ku te gotine çêkir feqat bi min resma jêbirinê baş nebû, tenê ya talî tê jêbirin ne tiştekî normal e? Min Beşên din jî xiste nav Eleqedar, Beşên din ne hewce ye. Bila zêde tab jî nebin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:11, 15 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}}
Îkona jêbirinê (min rind fêm nekir) li gor daxwaza xwe bike... Herwiha çend bug hene:
# Di nav maneyê de nivîs "zû" nayê nivîsandin.
# Dema ku tu beşê dikî "Navdêr", paşê jî "Lêker": wekî "Navdêr" dimîne. Tu ê li ser "Bilêvkirin" bitikînî.
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:18, 15 tebax 2026 (UTC)
:Min 1 fêm kir, ji ber ku Werger û Bi zaravayên din tên updatekirin dereng dimîne, belkî li şûna wextê nivîsînê, em bikin piştî nivîsînê ew ê zûtir be.
:Ya 2an min nikaribû bibînim çawa ev problem derdikeve, li ku derê wekî Navdêr dimîne piştî guhartinê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:40, 15 tebax 2026 (UTC)
::Min fêm kir lazim e li Bilêvkirinê pê lê bikî da ku biguhêre... Ez ê rast bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 15 tebax 2026 (UTC)
:::Tiştekî din :) Formatên "Bilêvkirin" (kare) û "Beş1" (nîv-oval) ne wekhev in. Herwiha formatên "+ Beş zêde bike" û "+ Mane zêde bike" jî ne wekhev in. Tercihên te ne an jî bug e? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:37, 15 tebax 2026 (UTC)
::::Ev nîv-oval ji min re gelek ecêb tê. Yên din lazim in yek şekil bin wekî +Mane zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:40, 15 tebax 2026 (UTC)
:::::Li şûna "Ev ne pênase ye — şablonek bi kar bîne" ez ê çi binivîsim? Ti tişt nayê bîra min ya ku dikare vê bikaranînê îzah bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:43, 15 tebax 2026 (UTC)
::::::Maneyê bi şablonan çêke (rastnivîs, şas nivîs, aweyekî din, ku-Latn, ...) :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 15 tebax 2026 (UTC)
:::::::Bi min "rehê niha" û "rehê borî" were dîtin ji ber ku bê wan ew lêker nayê bikaranîn. Tiştekî pirr hewce ye... Te "Cureyê lêkerê:" nivîsandiye paşe jî bin wan "Rehên lêkerê (Lêker çito tê tewandin?):". Herwiha Beşa "Tewandin" hertim şablona "ku-tewîn-nav" dide.
:::::::(?) Dever/Dovok jî bin Manyê were nivîsandin: Ew pey li ku derê an di kîjan devokê de tê bikaranîn: ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:45, 16 tebax 2026 (UTC)
::::::::An jî di bin beşa "Tewandin"ê de vana pirs bike....r [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:56, 16 tebax 2026 (UTC)
::::::::Herkes nikare "rehê niha" û "rehê borî" bizane, eger em "dema niha" û "dema borî" bixwazin hêsantir tê fêmkirin û bi zimanên din jî tê bikaranîn. Feqat [[Modul:rêziman]] wana destek dide? Nayê bîra min... Ez ê dîzayna Dever/devok ji design toolê pirs bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:57, 16 tebax 2026 (UTC)
:::::::::Erê, bi parametreyên <code>niha=</code>/<code>nihatr=</code> û <code>borî=</code>/<code>borîtr=</code>. <code>subj=</code>Bnr, {{q|fa|داشتن}}, {{q|ku|emilandin}}, {{q|ku|emilîn}}. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:02, 16 tebax 2026 (UTC)
::::::::::Wana dizanim, ew reh in, ez dibêjim forma dema niha, forma dema borî, em fniha, fbila û fborî çêkin? Bi tirkî, îngilîzî jî dikare were bikaranîn... dikim, bikim, kir. yapiyorum, yap, yapti, doing, do, did? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:06, 16 tebax 2026 (UTC)
:::::::::::Ahh, min niha fêm kir :) Paşê jî li gor wan em dikarin rehê lêrê çêkin... Na ew nehatine çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:17, 16 tebax 2026 (UTC)
:::::::::::min ceriband [https://ku.wiktionary.org/w/index.php?title=yapmak&diff=prev&oldid=6334114] werga çitonî ye? Ez dibêjim zêdetir kes dikare forma dema niha bizane û binivîse feqat herkes nikare reh fêm bike çi ye û çawa ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:19, 16 tebax 2026 (UTC)
::::::::::::Bi min qutiya nivîsa Maneyê wekî yê "Nimûne" mezin were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:50, 16 tebax 2026 (UTC)
:::::::::::::Prompt-ên me pir zeîf in, lazim e pêşî jê re Promptek bidin çêkirin dûre herin qismê Codeê û promptê bizelîqînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:32, 16 tebax 2026 (UTC)
::::::::::::::Êdî AI promptên ne baş jî fêm dikin û serrerast dikin, pêşta hukirina formata promptan hewce bû... Pirr pirs hatine pirs kirin; niha, ew dizanin kîjan rind e û tu çi dixwazî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:45, 16 tebax 2026 (UTC)
:::::::::::::::Format niha baş e feqet ew Gadjet ji bo kesên ku debî-ya înternetê yên wan hêdî ye rind dişuxile? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:58, 16 tebax 2026 (UTC)
::::::::::::::::(ji bo lêkeran) Bi min, bin "Lêker derbasdar e?" were nivîsandin wekî "Tewandina lêkerê". Pêş qutiyan jî "ez" an "min" (borî) ( li gor curyê lêkerê were nivîsandin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:04, 16 tebax 2026 (UTC)
::::::::::::::::In this sentence, debî means "(data) throughput" or "bandwidth" — internet connection speed. It's a loanword from French débit (as in débit internet), which is used in Kurdish tech/web contexts for how fast a connection is.
::::::::::::::::So the phrase "kesên ku debî-yên înternetê yên wan hêdî ye" translates to "people whose internet speed/bandwidth is slow" — i.e., people on slow connections.
::::::::::::::::The full sentence is Ghybu asking: "The format is good now, but does this Gadget run well for people with slow internet connections?" — essentially a performance/loading-speed concern, not a formatting one.
::::::::::::::::If you want, I can look at what might be slowing the gadget down for low-bandwidth users (e.g., how many separate resources it loads, any heavy assets) and suggest something before you reply to them.
::::::::::::::::yes take a look
::::::::::::::::Read 6 files, ran 2 commands, searched 2 patterns
::::::::::::::::I dug into what actually gets downloaded on a fresh load of the wizard. Breakdown of the four files:
::::::::::::::::File Size (raw / gzip)
::::::::::::::::start.js 270 KB / 77 KB
::::::::::::::::data.json 114 KB / 36 KB
::::::::::::::::styles.css 87 KB / 19 KB
::::::::::::::::util.js 16 KB / 6 KB
::::::::::::::::So roughly 490 KB of source, ~140 KB after gzip — that's the real number Ghybu is feeling on a slow connection, especially the first time they load the gadget after any update (MediaWiki caches it aggressively afterward via a versioned URL, so it's mainly a one-time-per-version cost, not every page load).
::::::::::::::::Two concrete findings:
::::::::::::::::A real, fixable bottleneck: the whole gadget's initialization is gated behind mw.loader.using(["oojs-ui-core", "oojs-ui.styles.icons-moderation"], ...) — meaning the wizard doesn't start rendering anything until both modules arrive. But icons-moderation is only there for the trash-can icon on the delete buttons, and those buttons don't even exist until someone has 2+ Mane or 2+ Beş sections. Right now every user pays that extra module round-trip before seeing the form at all, even though most people won't touch a delete button in the first few seconds. Deferring that one module load until the first trash button is actually built would let the form render sooner on a slow connection, for free.
::::::::::::::::Not really fixable without bigger surgery: data.json's "ziman" (language list) is 75 KB of its 114 KB — the single biggest chunk in the whole payload. But that's needed for the very first field in the wizard (the language picker), so lazy-loading it would just delay the first thing the person needs, not help. The only way to shrink this meaningfully would be turning the language field into a live-search that fetches matches instead of shipping the whole list upfront — a much bigger change, and probably not worth it unless load time is a serious complaint from multiple people.
::::::::::::::::Want me to make the icons-moderation fix? It's small and low-risk — I'd move that module load to fire lazily right before the first delete button is created instead of blocking the initial render. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 16 tebax 2026 (UTC)
:::::::::::::::::Erê :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:14, 16 tebax 2026 (UTC)
::::::::::::::::::-:) Em çend sed ziman hilbijêrin tenê wana nîşan bidin? Niha tenê yên ku lêmayên wan hene tên lîstekirin. nêzîkî 3k ziman
::::::::::::::::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:21, 16 tebax 2026 (UTC)
:::::::::::::::::::Bi min Jimar di tercîhên pêşketî de veşartî be. Zêde nayê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
::::::::::::::::::::"Tewandina lêkerê" rast çêbû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
:::::::::::::::::::::Erê jimar were veşartin. Tewandina lêkerê rind bûye feqet dema bilanî ji bo zazakî ye.
:::::::::::::::::::::Qutiya "Cureyê rêzimanî" pirr nêzîkî nivîsa "Lêker derbasdar e?" ye û dûr nivîsa jor e.
:::::::::::::::::::::Herwiha Gadjetê "Bi otomatîk tijî bike" pirr rind e feqet min bikaranîna wî zû fêm nekir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:25, 16 tebax 2026 (UTC)
::::::::::::::::::::::Min "Çend caran wekî dema bê nayê çêkirin... " fêm nekir. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:34, 16 tebax 2026 (UTC)
:::::::::::::::::::::::"Maneyê bi şablonan çêke (rastnivîs, şasnivîs, aweyekî din)" bi min lazim e pêşiya qutiya mane be, ji ber ku me devok, etîket kategorî zêde kir niha ecêb bûye [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
:::::::::::::::::::::::Bi formên standardê dema fermanî wekî forma sêyem (yekjimar) a dema bê ye (dema bê: ew ê/dê bibêje// fermanî: bibêje). Feqet di nav devokan de ne hertim wisa ye. Mînak bi devoka rojhilatî: ew dê bêt (dema bê) û bibê (dema fermanî). Li vira dema fermanî bi dema bê nayê çêkirin. Feqet ne hewce ye ji ber ku li jêr devok tê xwastin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
::::::::::::::::::::::::An jî dema bê were xwastin, dema fermanî wekî yê standardê tê çêkirin. Min tevlî hev kir :)) . Bi min bes e Gadjetekî tewandinê nîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:49, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::Em bilanî bikin "fermanî" zêde serê xwe neêşînin, wekî din em ê deh qutiyên din çêkin :D Piştî hinek clean-upê jî em biweşînin ji bo niha bes e {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:55, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::Problem, bi rastî, forma bê ye :)) Di çend devokan de pêşgira "bi-" nayê bikaranîn (torî, badînî, ... [[Modul:ku-tewandin#L-298]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::Te tabloya tewandina reşwî dît? [[Bikarhêner:Kurdanparez/sandbox]]. Şanseke me heye ku ew nekirine standard :). Feqet pirr entresan e, formên mê û nêr jî hene. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:23, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::::Qet behsa standardiyê meîne (meyîne?) :D Ji wî alî de gelek xemgîn im. Min ji bo kurmancî forma dema bê tevlî kir, fermanî ji bo zimanên din. Feqat Claude niha naxebite dibêje "Claude is temporarily unavailable" {{=D}} Niha divê çi were paqijkirin. Tiştekî kêm ma?
::::::::::::::::::::::::::::"All the code changes (jimar dropdown from data.json for every language, fdbe → fdbê rename with fdbêtr added, and the "Taybetmendî" heading removed) are done and verified on my end — nothing is lost" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::::Min placeholdera URL, DOI... guhart niha baştir tê fêmkirin înşela? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:07, 16 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::::}}Tiştên ku lazim in were guhartin:
* Placeholdera qismê "Tewandina "kelîme" binivîse"
[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:11, 16 tebax 2026 (UTC)
:Divê ns 108 destek bide. Wextê ns 108 be berîya beşa zimanî {{şx|jinûvesazkirî|langcode}} were zêdekirin, wekî din her tişt eynî ne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 16 tebax 2026 (UTC)
::"Ev mane li ku derê an..." bike "Bi vê maneyê li kû derê an..." [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 16 tebax 2026 (UTC)
:::Ji bo forma bê kesê sêyem were xwastin (ew ê/dê ...) ji ber ku di forma standardê de wekî forma fermanî ye: (tu) ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:22, 16 tebax 2026 (UTC)
:::Kitêb/kovar/websîte lazim e formata fireh kurt wekî [[MediaWiki:Gadget-wikt.add-examples.js]] bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:23, 16 tebax 2026 (UTC)
::::Butonên lînk û înfo ya dropdowna Etîmolojî û Bilêvkirin naxebitin. Ya dropdowna şablonên Maneyê jî du heb hene, yek di nav dropdownê de ku naxebitin ya din piştî hilbijartinê xuya dibin ku ew jî ne hewce ne, tenê lazim e di nav qutiyê de be. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:30, 16 tebax 2026 (UTC)
:::::Di "Tercîhên min" de butona "Hemû cache paqij bike" lazim e cache-yên etîket, dever û kategoriyê jî paqij bike. Niha nake. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:31, 16 tebax 2026 (UTC)
::::::Pill-ên Hevmane/Dijmane etc. lazim in di derveyî qutiya kelîmeyê de xuya bibin piştî nivîsandinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:33, 16 tebax 2026 (UTC)
:::::::"- beşek hilbijêre -" bike "- binebeşên din hilbijêre -" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:34, 16 tebax 2026 (UTC)
::::::::Çavkanî section dropdown linebreak dixe berîya "* şablon" lazim e linebreak piştî navê şablonê were. Îkonên lînk û înfo li vir jî naxebitin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 16 tebax 2026 (UTC)
:::::::::"Binêre herwiha (bnr-h)" bike "Herwiha binêre (h-bnr)"
:::::::::"Şablona bnr-h li qismê herî jor a rûpelê" bike "Şablona h-bnr li qismê herî jor a rûpelê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:36, 16 tebax 2026 (UTC)
::::::::::@[[Bikarhêner:Ghybu|Ghybu]] Bi min problem nema. Fikrê te? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:18, 17 tebax 2026 (UTC)
:::::::::::Temam. Çend tişt 1) Forma dema bê bin forma dema niha. 2) Beşa Tewandinê (ji bo lêkeran) vitira diminî an ji li gor danayên lêkerê (x-gh|reh, ...) tên tijî kirin. 3) Di qutiyên forma dema niha, borî, ... de zû nayê nivîsandin: înterneta te pirr rind e, tu ferq nakî? :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:46, 17 tebax 2026 (UTC)
::::::::::::Te wisa got înterneta min qut bû ew çi îş e :D Înterneta min jî pir hêdî ye feqat min ferq nekiribû. Eynî problem heye. Ji bo lêkeran tewandin bila niha wisa bimîne em dûre rojane bikin, jixwe piştî rastkirina şablonên jêgirtinan ez ê dîsa vegerim vê skrîptê. Niha lazim e bala xwe bidim jêgirtinan. Herwiha em êdî qerarek bidin tewandina lêkeran diçin beşa Tewandinê an beşa rêzimanê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:03, 17 tebax 2026 (UTC)
:::::::::::::Eger tenê yên x-gh/ngh dikî em dikarin tewandina wê niha zêde bikin, feqat p û h zor in ne wisa? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:25, 17 tebax 2026 (UTC)
== [[Mediawiki:Gadget-QQ.js]] ==
Ev amûr di şikle Minerva de. Her daîm vê xeletiyê dide:
<syntaxhighlight lang="js">
TypeError: Cannot read properties of null (reading 'addEventListener') at css () at runScript () at Array.<anonymous> () at flushCssBuffer ()
</syntaxhighlight> [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:29, 18 tîrmeh 2026 (UTC)
:* [[Bikarhêner:Wikihez/skrîpt/js/Gadget-QQ.js]] --> [[Mediawiki:Gadget-QQ.js]]
:* [[Bikarhêner:Wikihez/Gadget-QQ.css]] --> [[Mediawiki:Gadget-QQ.css]]
:Ez dibêjim çêbû lê ka çend roj biceribînim. Tiştên ku hatin sererastkirin:
:# Google Books API Key dikarî zêde bikî, dixe cookies û di gerokê de dimîne. Berê bê API Key dixebitiya lê êdî mecbûr e ku API Key-a te hebe, bidestxistina wê ne zêde zor e lê dîsa jî tevlihev e.
:# Êdî di mobîlê de dixebite, heta di mobîlê de xweştir xuya dike.
:# Moda tarî
:# OOUI dewsa jquery.ui
:# Li qutiyê çi dixwazî zêde bikî, dikare li gorî îsm, sifet, fiîl bitewîne. Lê ji bo fiîl tenê dikare çend heb inflection bîne.
:# Butona paqij bike lê zêde kir.
:# Çend paqijiyên jêgirtinên Google Kitêbê niha hene.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:38, 18 tîrmeh 2026 (UTC)
::Tewandina fiîlan û xeletiya modulê lazim e were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:40, 21 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-wikt.add-examples.js]] ==
Min ev amûr hinek rojane kir feqat çend problem hene berî ku bar bikin.
Tiştên ku min guhart:
# Êdî li sayfeyên bi {{ş|maneid}} jî dixebite,
# Cihê di wîkîtekstê de yê ku jêgirtin were îlawekirin baştir bû,
# [[Mediawiki:Gadget-citoid-helper.js|citoid helper]] zêde kir ji bo ku bi hêsanî çavkanî bi riya doi, isbn an urlyê werin tijîkirin.
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.css]] --> [[MediaWiki:Gadget-wikt.add-examples.css]]
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.js]] --> [[MediaWiki:Gadget-wikt.add-examples.js]]
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 19 tîrmeh 2026 (UTC)
:Min "wikt." qismên cache kirinê ya wîkiya fransî bi [[MediaWiki:Gadget-StorageUtils.js]] guhartiye. loma ji bîr nekin wê amûrê li [[MediaWiki:Gadgets-definition]] zêde bikin. û navê amûrê jî biguhêrînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:15, 23 tîrmeh 2026 (UTC)
::Berê li ser WF:ceribandin dixebitî. niha naxebite. Divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:10, 7 tebax 2026 (UTC)
== Gadget-translation editor ==
* [[MediaWiki:Gadget-translation editor.js]]: divê formata en.wîktê were bikaranîn ji ber ku me gelek tercime ji en.wîktê kopî kir û ev js hîn nikare formata wekî li jêr bi kar bîne.
<syntaxhighlight lang="wikitext">
* {{Z|sh}}:
*: Kirîlî:
*: Latînî
</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Mediawiki:Gadget-linkify.js]] ==
* ''rûpeleke'' wekî<syntaxhighlight lang="wikitext">[[rûpeleke]]</syntaxhighlight> lînk dike, bi regexê belkî dikare wisa bike <syntaxhighlight lang="wikitext">[[rûpel]]eke</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
* Amûreke din wekî [[User:Wikihez/Gadget-linkify.js|linkify]] lazim e ku herfên mezin biçûk bike ji bo kurmancî.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:{{r|Bikarhêner}} Merheba bira, dikarî viya jî bar bikî?:
* [[Special:PermanentLink/6291974|linkify]] --> [[Mediawiki:Gadget-linkify.js]]
Spas--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:42, 29 tîrmeh 2026 (UTC)
:Dûre [https://ku.wiktionary.org/wiki/MediaWiki:Gadget-start.js#L-283 li vir] wiktviLinkify bike kuLinkify xêra xwe [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:51, 29 tîrmeh 2026 (UTC)
::Min ji bîr kir lê zêde bikim niha nikare "Dİ" bike "di", xeletiyeke berbelav a di nivîsên kurdî de, herfên din ên wekî ll yan rr jî hewce ne werin zêdekirin. Dûre em zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:42, 29 tîrmeh 2026 (UTC)
:::Min nû ferq kir, sayfeyên ku hîn nehatine çêkirin wîkîlînk nake. Problemeke mezin e divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:15, 30 tîrmeh 2026 (UTC)
::::Na, lînk dike, tiştekî din heye... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:49, 31 tîrmeh 2026 (UTC)
== Amûra manayê ==
Amûreke din ji bo nivîsina maneyê wekî Nimûne zêde bike lazim e [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6276876 js] [https://ku.wiktionary.org/w/index.php?title=%C5%9Eablon:ceribandin2&oldid=6265629 şablon] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6265642 css] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:12, 23 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-jeder.js]] ==
@[[Bikarhêner:Ghybu|Ghybu]] Çavkaniyên jêderê di moda pêşdîtinê de jêderê vedişêre, ew jî min gelek eciz dike wextê çavkanî zêde dikim. Min ji Claude pirsî û kodan nîşanî wê da, wisa cewab da:
Yes — and this file actually explains the whole thing precisely, better than my earlier guess about extract-stripping.
What's happening: the CSS rule
<syntaxhighlight lang="css">
.jeder{
display:none;
}
</syntaxhighlight>
hides every citation by default, *unconditionally*. The only thing that ever makes it visible again is the gadget's JS: it finds each `.jeder`, builds the short "`— (year, author, +)`" summary from the `.j-pewist` fields, and either reveals the short version or — for citations with no `.j-pewist` fields — explicitly sets `display:inline` on the original.
So visibility of citations depends entirely on this JS running. In the normal article view (Image 1) it runs, so you see the collapsed "`(1918, E. Rehmî Ji Mala Hekariyan, Amadekar: M. E. Bozarslan, +)`" form. In whatever generated Image 2 — a hover-preview/extract/API render — gadget JS doesn't execute, so nothing ever removes the `display:none`, and the citation vanishes completely rather than degrading to "still visible, just not collapsed."
<b>The fix:</b> flip the default so the *un-collapsed* state is the safe fallback, and let JS do the hiding only after it successfully builds the short version. That way, any context without JS shows the full citation instead of nothing.
CSS — remove the blanket hide:
<syntaxhighlight lang="css">
.jeder {
/* intentionally no display:none here — see Gadget-... .js */
}
</syntaxhighlight>
JS — do the hiding from script, after the short form exists:
{{veşêre-ser}}
<syntaxhighlight lang="js">
// Ji bo şablonên [[Şablon:jêder]] hatiye nivîsandin.
// Parametreyên ku di navbera "<span class="j-pewist">...</span>" an "{{j-pêwîst|...}}" de ne venaşêre.
$(function() {
function nivisaKurtTevliBike(sund, nivisKurt) {
var zede = $('<a>+</a>')
.addClass('click')
.attr('href', '#')
.attr('title', 'zêdetir')
.css('font-weight', 'bold');
var kurt = $(' <a>↩</a>')
.addClass('click-kurt')
.attr('href', '#')
.attr('title', 'veşêre')
.css('font-weight', 'bold');
$('<span>' + nivisKurt + '</span>')
.append(zede).append(')')
.addClass('jeder-kurt')
.css('font-size', '0.85em')
.insertAfter(sund);
sund.append(' ').append(kurt);
// Only hide the full citation once the short summary actually exists.
// If this script never runs (no-JS clients, hover-preview extracts,
// action=render, etc.), the full citation stays visible instead of
// silently disappearing.
sund.css('display', 'none');
}
function vesere() {
$('.jeder').each(function() {
var elmTab = $(this).find('.j-pewist').clone();
if (elmTab.length) {
var elm = '';
for (var i = elmTab.length - 1; i >= 0; i--) {
elm = elmTab.eq(i).html() + ', ' + elm;
}
var nivisKurt = ' — (' + elm;
nivisaKurtTevliBike($(this), nivisKurt);
}
// else: no j-pewist fields to shorten — leave the full citation
// as it already renders (visible), nothing to do.
});
}
vesere();
$(".click").click(function(e) {
e.preventDefault();
$(this).parent('.jeder-kurt').css('display', 'none');
$(this).parent('.jeder-kurt').prev().css('display', 'inline');
});
$(".click-kurt").click(function(e) {
e.preventDefault();
$(this).parent('.jeder').css('display', 'none');
$(this).parent('.jeder').next().css('display', 'inline');
});
});
</syntaxhighlight>
Net effect:
- JS-enabled readers (normal article view): identical experience to today — collapsed short form with `+`/`↩` toggle.
- No-JS contexts (previews, extracts, screen readers before JS loads, print, etc.): full citation is shown instead of nothing — a real regression fix rather than a cosmetic one.
One caveat worth being upfront about: this fixes it *if* the preview context renders the gadget's HTML/CSS but doesn't execute gadget JS (which is the far more common case, and what this code strongly suggests is happening). If Image 2 instead comes from a pipeline that strips the `.jeder`/`.jegirtin-text` elements out of the HTML entirely before this CSS/JS even gets a chance to apply, no on-wiki CSS/JS change can help — that'd need the extract-generation config changed instead. Given what this gadget file shows, though, I'd bet on the CSS-hide explanation.
{{veşêre-bin}}
Em dûre biceribînin eger xeletî tinebe, vî tiştî jî rast bikin. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:00, 23 tîrmeh 2026 (UTC)
:Ez APIya MediaWiki rind nizanim belkî Claude dizane:) Em nikarin bibêjin dema ku moda "''action=edit''" e fonksiyona <code>function vesere()</code> bi kar meyîne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:57, 24 tîrmeh 2026 (UTC)
::Na, di moda "edît"-ê de <code>var elmTab = null</code>... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:13, 24 tîrmeh 2026 (UTC)
?:
<syntaxhighlight lang="js">
if (
mw.config.get('wgAction') === 'edit' ||
mw.config.get('wgAction') === 'submit'
){
var elmTab = [];
} else {
var elmTab = $(this).find('.j-pewist').clone();
}
</syntaxhighlight>
--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:31, 24 tîrmeh 2026 (UTC)
:Ez jî nizanim Claude çiqas ji mediawiki fêm dike lê heta niha ti tişt nîne ku min jê pirs kir û nikaribû çêke, bila wekî reklam nebe feqat Claude mikemmel e, dibêjim "li vir çima xeletiyeke wisa derdikeve" di dosyeya 2000 rêzî de diçe tiştê xelet tenê rast dike tiştekî din naguherîne. Chatgpt û aistudio qet wisa nakin, eynî dosyeyê deh cara dinivîsin, deh netîceyên cuda derdikevin :D
:Em dikarin vê amûrê wextê edit û submitê neaktîv bikin wekî <syntaxhighlight lang="js">if (!(action === "edit" || action === "submit")) {
:return;</syntaxhighlight> lê wê demê display:none wê çibe nizanim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:23, 24 tîrmeh 2026 (UTC)
::display:none ne hewce ye ma jixwe di nav kodê [https://ku.wiktionary.org/w/index.php?title=MediaWiki:Gadget-jeder.js&oldid=6069025#L-49 L-49] de inline/none heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:53, 24 tîrmeh 2026 (UTC)
:Min bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.js&diff=prev&oldid=6290816#L-28] ceriband feqat nexebitî. "display:none" di pêşdîtinê de dixebite feqat js naşixule. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:43, 25 tîrmeh 2026 (UTC)
::Kodên JSê pir hez nakim (pêşta hukurina APIya MWyê hewce bû) dîsa jî ez ê seh bikim :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:49, 27 tîrmeh 2026 (UTC)
:::Ez niha bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6290820] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6290821#L-8] bi kar tînim ti problem nîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:09, 27 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-PagePreviews.js]] ==
Ez dibêjim em vê amûrê jî çêkin [[:en:MediaWiki:Gadget-PagePreviews.js]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:56, 3 tebax 2026 (UTC)
= Moda tarî =
== [[Şablon:wîkîpediya]] ==
Rengê tarî de xirab e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:36, 4 tebax 2026 (UTC)
= Bot =
Karên ku bi botê werin çêkirin ji kerema xwe li vir binivîsin.
== Kategorî:Biwêj ==
[[:Kategorî:Biwêj]] divê were paqijkirin, lîsteya madeyên {{ş|biwêj}} çêke, yên ku tê de bûn, kirin, man hene filtre bike yên din bi destî tê çêkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Jêgirtin ==
Xistina jêgirtinên beşa [https://ku.wiktionary.org/w/index.php?search=insource%3A%2F%3D+*Ji+%5BWw%5D%C3%AAjey%C3%AA+*%3D%2F&title=Taybet%3AL%C3%AAger%C3%AEn&profile=advanced&fulltext=1&ns0=1&searchToken=8rh0bmw8dccsi7aynl6zli8uf ji wêjeyê] li beşa maneyê. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Bota Toolforgeê ==
Botekî Toolforgeê lazim e ji bo ku xeletiyên biçûk wekî şablonên bi navên îngilîzî werin kurdîkirin yan jî valahiya beşan rast bike û hwd.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:Min [[Bikarhêner:Wikihez/skrîpt/py/kuwiktkozmetik.py|paqijiyen kozmetîk]] hazir kir feqat hîn navên şablonan naguhêre. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 4 tebax 2026 (UTC)
== Updatew ==
Bota [[Bikarhêner:Wikihez/skrîpt/py/updateW.py|updateW]] divê were rojanekirin. Yan jî yekî nû were çêkirin. Şablonên {{ş|W}} di beşa tercimeyan de sererast dike û zimanan rêz dike. Ya niha xeletiya navê zimanî dide.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== sihir --> sêhr ==
Tercimeyên ''[[sêhr]]'' hemû dibêje ''sihir'' loma bi botê lazim e kirin ''sêhr''. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:43, 18 tîrmeh 2026 (UTC)
:Sihirbaz --> sêhrbaz [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 18 tîrmeh 2026 (UTC)
== Tercimeyên dîroknas ==
Li tercimeyên [[dîroknas]], hevmaneyên wê hewce ye were îlawekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:44, 18 tîrmeh 2026 (UTC)
== Mewlûd ==
Sererastkirina jêgirtinên mewlûdê [https://ku.wiktionary.org/w/index.php?search=insource%3A%2Fw%5C%3AMela+His%C3%AAn%C3%AA+Batey%C3%AE%2F&title=Taybet%3AL%C3%AAger%C3%AEn&ns0=1&searchToken=8m0tguy3sqt7edr4sg0u8hedo] wekî [https://ku.wiktionary.org/w/index.php?title=dax%C3%AEdar&curid=284303&diff=6265851&oldid=6142285] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:43, 20 tîrmeh 2026 (UTC)
== [[Şablon:jêder]] ==
Şablon jêder axlebî bi şiklê MM/YYYY sal tê de heye, bi botê em dikarin wana bixin {{para|sal}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:07, 17 tebax 2026 (UTC)
n3wyet9cqs38qvaosbgg1hw3fiwhx5z
6334377
6334376
2026-08-17T18:28:28Z
Wikihez
7344
/* Mediawiki:Gadget-QQ.js */ Bersiv
6334377
wikitext
text/x-wiki
{{/ser}}
= Karên ferhengî =
== Mane ==
* [[:Kategorî:Madeyên bê beşa mane]]
** [[:Kategorî:Madeyên bê beşa mane bi kurmancî]]
Li ser van rûpelan ev made hene ku maneyan wan nehatine nivîsin û divê werin nivîsin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Etîmolojî ==
*[[:Kategorî:Daxwazên etîmolojiyê]]
**[[:Kategorî:Daxwazên etîmolojiyê bi kurmancî]]
Li ser van rûpelan ev made hene ku etîmolojiya wan hîn nehatine nivîsin. Ji bo alîkariyê binêre [[WF:Etîmolojî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Cureyê rêzimanî ==
* [[:Kategorî:Gotarên bê kategoriya rêzimanî]]
* [[:Kategorî:Gotarên bê kategoriya rêzimanî bi kurmancî]]
Li ser van rûpelan ev made hene ku cureyên wan yên rêzimanî hîn nehatine nivîsin. Ji bo alîkariyê binêre [[Alîkarî:Cureyên peyvan]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Kontrol ==
* [[:Kategorî:Kontrol]]
** [[:Kategorî:Kontrola maneyê]]
Ev madde bi botê hatine çêkirin, yek bi yek divê werin kontrolkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
= Şablon =
== IPA ==
* {{ş|IPA}} divê [[Modul:IPA]] bi kar bîne. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Deng ==
Kurdîkirina belgekirina {{ş|deng}} lazim e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 19 tîrmeh 2026 (UTC)
= Modul =
== Modul:form of/templates ==
* [[Modul:form of/templates]]: Divê ev modul were rojanekirin û xeletiyên berê wekî ([[hevalê]]) werin rastkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:Merheba @[[Bikarhêner:Ghybu|Ghybu]] Te gotibû di vê modulê de xeletî hene, jkx tu dikarî li vir binivîsî çi kêmasî hene, daku wextê rojane dikim ez ji bîr nekim? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:16, 21 tîrmeh 2026 (UTC)
*::Silav. Nizanim ji bîra min çûye... Herwiha ji bo çêkirina formên navdêran zanînên cîns û tîpa dawî bes e:
*::* (-ê, mê) => çemandî, bangkirin
*::* (-ê, nêr) => îzafe
*::* (-ê, mê û nêr) => îzafe (nêr), bangkirin (mê), çemandî (mê)
*::* (-a) => îzafe (mê)
*::* (-ekî) => îzafe û çemandî (nêr, nebinavkirî)
*::* (-eke) = > îzafe (mê, nebinavkirî)
*::* ...
*:: <code>if find(mw.title.getCurrentTitle().subpageText, 'ekî$') then ... end</code>
*::<code><nowiki>{{ku-tewîn-navForm|<navdêr>|<zayend>}</nowiki></code>
*::Tablo û nivîs di yekcarî de tên çêkirin...
*::Feqet problem lêzêdekirina mînakan e. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:34, 21 tîrmeh 2026 (UTC)
*:::Lêzêdekirina kîjan nimûneyan? Ev koda js ji bo çi ye? Qisûrê nenêre min fêm nekir :/ [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:13, 21 tîrmeh 2026 (UTC)
*::::Li vira ([[hevalê#Formeke navdêrê]]) di bin maneyên 1. û 2. mînak nayên tevlî kirin :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 21 tîrmeh 2026 (UTC)
*:::::Ev tercîh e, ne mecbûrî ye, em dikarin [https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&diff=prev&oldid=6276350 wisa] bikin. Wekî din? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:29, 21 tîrmeh 2026 (UTC)
*::::::Min li jor tênê got ku tenê parametreyên "navdêr" û "zayend" hewce nee. Bi '''[//ku.wiktionary.org/w/index.php?title=heval%C3%AA&curid=969734&diff=6276353&oldid=6276350 vî]''' awayî jî tê çêkirin. Parametreyên din ne hewce ne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:38, 21 tîrmeh 2026 (UTC)
*:::::::Ji bo wê şabloneke din lazim e, bila ev şablon wekî îngilîzî bimîne em ji bo kurmancî yekî din çêkin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:42, 21 tîrmeh 2026 (UTC)
*::::::::[https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&oldid=6276885 wisa] çawa ye? Bi min nivîsîna nimûneyan zêde ne lazim e ji ber ku ti kes serê xwe pê naêşîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:46, 23 tîrmeh 2026 (UTC)
*:::::::::@[[Bikarhêner:Ghybu|Ghybu]] Vê şablonê bi kar bînin an na? Navê şablonê kê çi be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:07, 24 tîrmeh 2026 (UTC)
== Modul:columns ==
* [[Modul:columns]] were rojanekirin û {{ş|kol3}} bila lînka ''nîşan bide'' deyne beriya sernavê, kolê di qutiya rengî de bila nebe. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:etymon ==
* [[Modul:etymon kevn]]: Rojanekirin hewce ye. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Modul:ziman/code to canonical name]] ==
Belgekirina skrîptê û js divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:31, 22 tîrmeh 2026 (UTC)
:* [[MediaWiki:UpdateLanguageNameAndCode.js]]
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:50, 22 tîrmeh 2026 (UTC)
::* [[Modul:ziman/print]]
::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:52, 22 tîrmeh 2026 (UTC)
:::*[[Modul:ziman/şablon]]
:::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:56, 22 tîrmeh 2026 (UTC)
::::* [[MediaWiki:Gadget-translation editor.js/ziman.json]]
::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:06, 22 tîrmeh 2026 (UTC)
== [[Modul:quote]] ==
Piştî ev modul hate hazirkirin, lazim e navên jegirtinan werin rastkirin û li hemûyan {{param|jêgirtin}} were zêdekirin. Bnr. [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Mem û Zîn]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 5 tebax 2026 (UTC)
::Ev modul hema bêje hazir e. Hûn li ser van madeyan çi difikirin:
# {{para|tarîx}} lazim e full date be eger na, lazim e {{para|sal}} were bikaranîn. Feqat "'''2021''' kanûna pêşîn 23" bi vî şiklî bi min ne problem e çnkî derdê en.wikt "attestation" e delîla herî pêşî jî sal e.
#: {{demo|sep=wisa xuya dibe<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|tarîx=23 December 2021|nivîskar=Harry Hobbs; George Williams|sernav=Micronations and the Search for Sovereignty|url=https://www.cambridge.org/core/books/micronations-and-the-search-for-sovereignty/B0414A3A322695D9EE71E4CDDFDA06DE|weşanxane=Cambridge University Press|rûpel=155|isbn=978-1-009-15012-5|jêgirtin=jêgirtin.|t=-}}</nowiki>}}
#{{para|amadekar}}/{{para|edîtor}}/{{para|terciman}} bi şiklê edîtoriya, amadekariya, tercimekirina
#: {{demo|sep=dide<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|sernav=Medical Surgical Nursing|serî=Juta's Manual of Nursing|cildê-seriyê=volume 4|edîtor=Sophie Mataniele Mogotlane; Joyce Mokoena; Motshedisi Eunice Chauke; Anne Young|sal=2005|weşanxane=Juta Academic|isbn=978-0702171840|rûpel=!12-29|jêgirtin=jêgirtin|t=-}}</nowiki>}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:09, 10 tebax 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Hîn gelek tişt hene divê îzah bikin û fikrê we pirs bikim, feqat îzahkirin pir zor e, em dest bi bikaranînê bikin û dûre tiştên ku hûn naxwazin em dikarin biguherînîn, dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:11, 10 tebax 2026 (UTC)
::Dibe, piştî çêbû, em di praktîkê de bibînin ku çawa ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:05, 10 tebax 2026 (UTC)
= Javascript =
== [[Mediawiki:Gadget-citoid-helper.js]] ==
Ji bo bi citoidê kopîkirina malûmatên çavkaniyan amûreke wisa lazim e. Xisûsen ji ber ku bi çend amûrên din dikare were bikaranîn.
Feqat çend problem hene, şablonên me de kêmasiya parametreyan gelek in û xerîteyên citoidê hîn nehatine çêkirin. Wana sererast bikin dûre vê amûrê çêkin, piştre [[MediaWiki:Gadget-start.js|start.js]] û [[MediaWiki:Gadget-wikt.add-examples.js|add examples.js]] were rojanekirin.
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:04, 19 tîrmeh 2026 (UTC)
:Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]], ez dibêjim karê Modul:quote zêde wext bistîne, bila em dest bi bikaranîna van amûran bikin dûre kêmasiyan em ê temam bikin înşela. Dikarî vana li cihên xwe yên rast kopî paste bikî?
# [[Special:PermanentLink/6291574|Bikarhêner:Wikihez/skrîpt/js/Gadget-citoid-helper.js]] --> [[Mediawiki:Gadget-citoid-helper.js]]
# Ya li jêr kopî bike qismê herî dawî ya AmûrênVeşartî li [[MediaWiki:Gadgets-definition]]:
## <code><nowiki>* citoid-helper[ResourceLoader|hidden|default|dependencies=mediawiki.util]|citoid-helper.js</nowiki></code>
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:58, 26 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-start.js]] ==
Rojanekirin hewce ye. Min gelek tişt lê zêde kir lê ez niha test dikime. Piştre em bar bikin:
* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 17 tîrmeh 2026 (UTC)
:Min [[MediaWiki:Gadget-start/data.json|data.json]] çêkir ji bo ku bi hêsanî malûmatên heyî werin guhartin bêyî guhartina Javascriptê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:49, 18 tîrmeh 2026 (UTC)
:Xeletiyên ku ji bîr nekim rast bikim:
:* Wextê min çavkanî "kitêb" hilbijart û bi destî tije kir xeletiyeke [https://ku.wiktionary.org/w/index.php?title=xezete&diff=prev&oldid=6265392 wisa] derket her kelîme hate lînkkirin.
:* <code>html("⠿")</code> ev ne lazim e.
:* textarea ji bo çavkaniyên jêgirtinan xaseten yên sernav hîn jî ne draggable e.
:* pêşdîtina zindî checkbox wextê tê rakirin, pêşdîtin winda nabe.
:* Butona mobîlê ya vekirina formê di moda tarî de belî nabe, tenê wekî qutiyeke tarî xuya dibe.
:* Metna hişyarkirinê di guhartina rûpelên heyî de lazim e baştir bikim.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:51, 18 tîrmeh 2026 (UTC)
::Ev problem çêbûn, ez ê binêrim ka çi problemên din hene. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:15, 19 tîrmeh 2026 (UTC)
:::Çêke: Ji bo qismê çavkaniyê yek dikare API call û Kategorî:Çavkanî bi kar bîne ji bo ku şablon zêde bikî ji yên heyî. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 19 tîrmeh 2026 (UTC)
::::Ev jî çêbû, min beşa çavkanî jî çêkir. Eger xeletî dernekeve û problema valahiya piştî etîketê ji bîr nekim, piştî rojanekirina amûrên linkify, û citoid em dikarin bar bikin. Ez ê ti tiştê nû îlawe nekim, zêde dirêj bû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:14, 21 tîrmeh 2026 (UTC)
:::::Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]]. Dikarî wana bar bikî?
:::::* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
:::::* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
:::::* [[MediaWiki:Gadgets-definition]] wisa rojane bike:
:::::* start[ResourceLoader|default|package|dependencies=mediawiki.util,ext.gadget.StorageUtils,ext.gadget.citoid-helper]|start.js|start.css|start.js/data.json
:::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:39, 5 tebax 2026 (UTC)
{{şirove}} Dewama ''[[Special:PermanentLink/6333688|Gotûbêja bikarhêner:Rewiye tine#WF:Nû]]'' bila gotûbêja wî em spam nekin. :D
{{r|Ghybu}} dikarî [[Special:PermanentLink/6333689|vê htmlê]] binêrî claude rast fêm kiriye yan na? Daxîne û wek html qeyd bike. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:43, 14 tebax 2026 (UTC)
:Min seh wîkîyên din jî kir, li wîkiya fransî tiştekî wekî daxwaza min çêkirine (tiştên ku ne hewce ne wekî "tab" çêkirine an jî hatine vêşartin): [//fr.wiktionary.org/w/index.php?title=fdsdfdsf&action=edit&redlink=1]. Formata me yê berî jî bi vê lojîkê hatibû çêkirin, wekî de.wiktionary ([//de.wiktionary.org/w/index.php?title=sdfdsfsdfs&action=edit&preload=Vorlage:Formatvorlage_(Substantiv_vereinfacht)]) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:01, 14 tebax 2026 (UTC)
::PS: Dîsa jî Claude min rind fêm kiriye :)) Feqet min got ku beşên Mane", "Bilêvkirin" û "Beşên din" cem hev (wekî "tab") ". Û dema ku tu terî di nav "Besên din" Hevmane, Jê, ... li wira werin dayîn. Wêne/Resim û Nimûne jî di nav beşa ("tab") "Mane" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:17, 14 tebax 2026 (UTC)
::Erê ya fr.wikt min jî dîtibû jixwe fikrê tab min ji wan girtibû :D (feqat min li cem wan tenê tab-a maneyê dît yên din nedît çawa tê vekirin nizanim) Berê hemû beş piştî maneyê bibûn, dûre min sê Tab çêkir, Bingehîn+Lînkkirin+Bikaranîn. Niha lazim e Tabeke din çêkim. Ya ku claude çêkiriye bnr. [[Special:PermanentLink/6333698#L-7]] ne baş e bnr. [[Special:PermanentLink/6333703|qiyaskirinê li ser vê htmlê]] Êdî ez Claude abuse dikime ji ber ku Pro ye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:26, 14 tebax 2026 (UTC)
:::Claude dibêje:
:::Good clarification — that's a real simplification, not just a relabel. Updated STEP_GROUPS from 5 tabs down to 3:
:::Mane — the definitions themselves, plus Wêne (image), now grouped in with it instead of sitting in a separate misc tab. Nimûne needed no change since it was already rendering inside each Mane sense block, not as its own step.
:::Bilêvkirin — unchanged, still just pronunciation.
:::Beşên din — everything else in one flat bucket: hevmane, jê, têkildar, herwiha binêre, formên alternatîv, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, tewandin, etîmolojî, zaravayên din, werger, and bnr-h — instead of being split across the old Bingehîn/Lînkkirin/Bikaranîn/Din tabs.
:::No DOM-order changes were needed this time — the fix from the blank-tab bug looks up the tab bar's position dynamically now, so it didn't matter that I regrouped which steps go where. Same two files, saved to your Wiki folder — worth another pass through all three tabs once you've pasted this in, same as last time. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:34, 14 tebax 2026 (UTC)
::::Mînak:
Ziman
---------------------------------------------
Cureyê rêzimanî (Navdêr)
Navdêr (hilbijêre) ┆ Cîns (hilbijêre) ┆ Jimar (hilbijêre)
- lêker -mê -yekjimar
- rengdêr -nêr -pirjimar
- ... -... -...
---------------------------------------------
Mane || Bilêvkirin || Beşên din
hilbijêre:
- mane - IPA - Hevmane
- nimûne - kîte - Dijmane
- wêne
Dema ku tu "Cureyê rêzimanî" dikî "Lêker" :
Cureyê rêzimanî (Lêker)
Lêker (hilbijêre) ┆ Cure (hilbijêre)
- navdêr -rehê niha
- rengdêr -rehê borî
- ... -...
---------------------------------------------
┆ : select/option ("tab" nîne, tê dîtin)<br>
|| : "tab"
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:45, 14 tebax 2026 (UTC)
'''Sê-stûnî ("┆" = ne tab), li gorî Cureyê rêzimanî:'''
{| class="wikitable"
! Cureyê rêzimanî !! Stûna 2 !! Stûna 3 !! Têbînî
|-
| Navdêr / Serenav
| Cîns (hilbijêre ▾: mê / nêr / ... — tenê heke ziman zayendê rêzimanî hebe)
| '''Jimar''' (?) — pirs li jêr
| Qadên "Forma mê/nêr/pirjimar/yekjimar" niha jî li vir hene — ma ev di "Jimar"-ê de tên tevlihev kirin, an cuda dimînin?
|-
| Rengdêr
| (heman m/n/p/y forman ku Navdêr distîne)
| Sûperlatîv (sûp) / Komparatîv (komp)
| Ne li ser her du nîgaran hatiye behs kirin — ma sê-stûnî li vir jî tê bikaranîn?
|-
| Lêker
| '''Cure''' = Cureyê lêkerê (hilbijêre ▾: gerguhêz / negerguhêz / xwerû-... / pêkhatî-... / hevedudanî-... / biwêjî-... — 10 vebijêrk)
| Di heman stûnê de: Rehê dema niha / Rehê dema borî / Rehê dema bilanî (her sê qadên nivîsandinê ne, ne hilbijêrk in)
| Ma "Cure" her sê rehan di xwe de digire wek yek stûn, an her yek stûneke cuda ye?
|-
| Tîp (herf/sembol)
| Girdêk (gir) / Hûrek (hûr)
| —
| Ne behs kiriye
|-
| Her cureyek, li gorî zimên
| Transliterasyon (tr) / Transkrîpsiyon (ts)
| —
| Bi zimên ve girêdayî ye, ne bi cureyê rêzimanî ve
|-
| Her cureyek, tenê ziman=japonî
| Hanja / Translit. hanja
| —
| Ne bi cureyê rêzimanî ve girêdayî ye
|-
| Her cureyek
| '''Tercîhên pêşketî''' (kolapsî): form, formaalt, nekatcure, nekatcins, nekatçendterm, serlînkneke, nekatpalîndrom, neformtr
| —
| Vebijarkî, ji bo hemû cureyan
|}
'''Tabên jêrîn — Mane ‖ Bilêvkirin ‖ Beşên din ("||" = tab):'''
{| class="wikitable"
! Mane !! Bilêvkirin !! Beşên din (hemû 18 hêman)
|-
| mane, nimûne, wêne
| şablonên bilêvkirinê (mînak: IPA, kîte)
| tewandin, etîmolojî, bi zaravayên din, werger, jê, têkildar, herwiha binêre, formên alternatîv, hevmane, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, bnr-h
|}
'''Pirsên vekirî:'''
# "Jimar" (yekjimar/pirjimar) — qadeke ku nîşan dide têketî bi xwe yekjimar e an pirjimar e. Ev cuda ye ji "Forma yekjimar (y)"/"Forma pirjimar (p)" ya heyî (ku forma DÎN a kelîmeyê tomar dike). Herdu bimînin, an werin yek kirin?
# Ma sê-stûnî tenê ji bo Navdêr/Lêker e, an ji bo Rengdêr/Tîp jî heman mantiq derbas dibe?
# Di "Beşên din" de: dixwazî pêşî navnîşek/hilbijartinek nîşan bide (17 hêmanên din jî tê de), û qad tenê piştî hilbijartinê xuya bibin?
:Tu dikarî start.js ji Tercîh/ Amûran rakî û bi test.js li Amûran herî taliyê ye biceribînî. Claude ya li jor hazir kir, Jimar ez dikarim îzah bikim. Feqat bi min di tab-a maneyê de lazim e Bi zaravayên din (heta wek werger her car bila li rûpelan were zêdekirin) û werger jî hebe.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:18, 14 tebax 2026 (UTC)
:;Navdêr: Tenê "cîns" û "jimar" bes e. "Forma mê", "Forma nêr", ... nayê fêm kirin. Bi min wekî "Tercîhên pêşketî" bi navkî din veşartî çêke.
:;Lêker: "Cureyê lêkerê" nayê fêm kirin, bi min pêşta "geguhez/negerguhêz" were hilbijartin, paşê jî form "hevdudanî/pêkatî/..." ji ber ku her kes vana nizanî... Dîsa jî tu dizanî.
:Bi min beşên "Hevmane" û dijmane jî bike de nav beşa Mane ("tab") ji ber ku pirr tên xwestin.
:Şûnda Wêne jî Nimûne/jêgirtin...
:Herwiha hemû bi rengê şîn hatine çêkirin, nizanim beşên din (===) bi rengê gewr an reş çito dibe? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:35, 14 tebax 2026 (UTC)
::Nimûne/jêgirtin, hevmane, Dijmane li binê her maneyê hene. ji ber ku wana bi maneyê ve girêdayî ne lazim e wisa bimîne feqat belkî şiklekî baştir bibînin. Hînbûna bikaranîna formeke nû bi qasî hînbûna koda wîkiyê zor e heqîqeten... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 14 tebax 2026 (UTC)
:::Min şaş fêm meke feqet te amûrek ji bo Wikihez çêkiriye :) Tu dixwazî ku hemû tiştên ku te çêkiriye tevlî vê Gadgetê bikî û bikarhênerên din jî bi kar bînin... Tu jî dizanî ku kesên ku tên vira çito peyv çêdikin: Ziman > Mane > Mînak > Hevmane > Dijmane ! Du Gadget çêke yek ji bo bikarhênerên ku hertim li vira ne, yekeke din jî ji bo bikarhênerên din (yên ku pirr nên vira).
:::Herwiha kesên ku hertim li vira ne bi Gadgetê gotar çênekin, ''copy/paste'' dikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 14 tebax 2026 (UTC)
::::Min xwest her tişt li ser rêza xwe be ji bo ku hîn bibin çi diçe kû derê feqat bi vî şiklî jî zêde dirêj û tevlihev dibe. Loma ez ê wekî tu dibêjî bikim her tiştê din bila collapsed pêşketî be. Ê ku dixwaze pê lê ke û binêre çi dikare were zêdekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 14 tebax 2026 (UTC)
:::::Niha baştir tê fêm kirin {{=)}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:01, 14 tebax 2026 (UTC)
::::::Min ji Opus 5 û Design Tool îstifade kiriye hema bêje milyonek token çûn :D pêşî dîzayn kir dûre dest pê kir wekî dîna bişuxile. Pereyê xwe %100 heq dike welleh :D feqat moda tarî ji bîr kiriye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:12, 14 tebax 2026 (UTC)
:::::::Spas! Etîmolojî jî wekî "Bilêvkirin", di beşekê din de cem wê, were çêkirin (Mane|Etîmolojî|Bilêvkirin|Beşên din)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:20, 14 tebax 2026 (UTC)
::::::::(Mane>Tewandin>Werger|Etîmolojî>Dûnde>|Bilêvkirin(formatek çawa?)>Resim|Eleqedar>Jê>Têkildar>Herwiha binêre|Beşên din)? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:29, 14 tebax 2026 (UTC)
:::::::::Nizanim, biceribîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:43, 14 tebax 2026 (UTC)
::::::::::"Li şablonekê bigere" ser Maneyê hatiye nivîsandin, nizanim rind tê fêm kirin an na? Min paşê fêm kir :)) Naskirina Wîkîferhengê hewce ye... Li bin were nivîsandin û "notek" hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:02, 15 tebax 2026 (UTC)
:::::::::::"Malûmatên pêşketî nîşan bide" jî bike ser "Hevman" ji ber ku pirr li jor e. Tu nizanî çi dike (nayê dîtin)... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:09, 15 tebax 2026 (UTC)
::::::::::::Dever/Devok jî were nivîsandin ? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 15 tebax 2026 (UTC)
:::::::::::::Tistekî din, dema ku tu maneyê dinivîsî bi otomatîk li jêr mane "2" tê nivîsandin, ew jî ne baş e (pirranî ne hewce ye). Bi min wekî "tab" cem "Mane 1" were vekirin (MAne1|Mane 2|Mane 3), bi otomatîk û an jî bi tikandina ser "Maneyeke din lê zêde bike" (cem beşa Maneyê). An jî bin beşa Wergerê bi nivîsa "maneyeke din lê zêde bike" were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:44, 15 tebax 2026 (UTC)
::::::::::::::Ji bo mane2, mane3, ... problemek heye: "Beşên din" ji bo kîjan maneyê ye :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:49, 15 tebax 2026 (UTC)
:::::::::::::::Devok/dever ya rastî ji bo kurmancî herî muhîm e lê diçe qismê etîketê ew jî veşartî ye, Niha çawa çêbûye? Min butona +mane zêde bike îlawe kiriye. Beşên din ji bo hemû maneyan e, ji bo maneyên xisûsî lazim e yek qismê malûmatên zêdetir bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:01, 15 tebax 2026 (UTC)
::::::::::::::::mane1, man2, ... cem beşa "Mane" çito ye, wekî:
::::::::::::::::<poem>
:::::::::::::::: '''Mane''' || Etîmoljî || Eleqedar || Beşên din
:::::::::::::::: -----------------------------------------------------------------
:::::::::::::::: Mane 1 || '''Mane 2''' || + Mane zêde bike [[Wêne:OOjs UI icon trash-destructive.svg|18px]]
:::::::::::::::: <small>Maneya kelîmeyê binivîse. Mane otomatîk tê ...</small>
::::::::::::::::┌───────────────────────────────────┐
::::::::::::::::│ Kelîmeyên ji bo... │
::::::::::::::::└───────────────────────────────────┘
:::::::::::::::: -----------------------------------------------------------------
::::::::::::::::</poem>
::::::::::::::::Belkî çêkirin zor e... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:59, 15 tebax 2026 (UTC)
:::::::::::::::::Ev nimûne çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:05, 15 tebax 2026 (UTC)
::::::::::::::::::Te beşa "Bilêvkirin" kiriye cem "Beş1", erê. Li gor vê lojîkê "Etîmlojî" jî were cem beşa "Bilêvkirin", na? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:19, 15 tebax 2026 (UTC)
::::::::::::::::::Etîmolojî h4 e Bilêvkirin h3 loma min Bilêvkirin xiste cem "Beş1". Tu dibêjî îzaha qismê Maneyê "Maneya kelîmeyê binivîse. Mane otomatîk tê" li bin tabên Maneyê bin ne wisa? Pir ferq dike? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:23, 15 tebax 2026 (UTC)
:::::::::::::::::::Checkbox ji bo Etîmolojiya "Beş2" zêde bikim? Eger truthy be bila Etîmolojî h3 be û here pêşiya crz (cureyê rêzimanî em jî hinek kurtkirin bi kar bînin êdî) ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 15 tebax 2026 (UTC)
:::::::::::::::::::Na min tiştekî witira negot, min tenê ji bo fêmkirinê ew şemayê çêkir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:27, 15 tebax 2026 (UTC)
::::::::::::::::::::OK, niha rind bû :) "Maneyeke din zêde bike" çito ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 15 tebax 2026 (UTC)
::::::::::::::::::::Temam wê demê her tişt çêbû... Însan hîn dikare gelek tişt zêde bike, feqat usage limits hene :D Min session-a 5 saeatî temam kir. Dûre ez ê wisa bikim wextê pê li taba Mane-Etîmolojî dikî here jor, bi vî aawayî piştî dagirtina her tiştî li jêr însan dikare bi hêsanî maneyên din binivîse. Pişhingî jî ez ê paqiya zimanî bikim, piştî hinek ceribandinan em nû bikin. Eger fikrên nû were dûre dîsa dikarin îlawe bikin :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:35, 15 tebax 2026 (UTC)
:::::::::::::::::::::Temam! Dema ku tu "Mane 2" an "Mane 3" bi şaşî nivîsand... çito tê/n jêbirin? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 15 tebax 2026 (UTC)
::::::::::::::::::::::Vala bikî bes e, beşên vala dernakevin li outputê. butonek çêkin? li kû derê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:49, 15 tebax 2026 (UTC)
::::::::::::::::::::::Cem "Maneyeke din zêde bike" '''jêbibe''' were nivîsandin an jî wêneyê [[Wêne:OOjs UI icon trash-destructive.svg|22px]]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:51, 15 tebax 2026 (UTC)
:::::::::::::::::::::::Mane 1 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] Mane 2 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] +Maneyeke din zêde bike çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 15 tebax 2026 (UTC)
::::::::::::::::::::::::Nizanim kîjan nîşan baş e: va jî heye: <code>✕</code> [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:03, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::Bi min tenê yek carê were nivîsandin ji ber ku tu bi şasî maneyên xwe jî jê dibirî (dema ku tu li ser maneyê ditikînî) :) Li cem "Beş zêde bike" jî were nivîsandin [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::Bi kurdmancî "Rehê dema bilanî (subj)" ne hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:48, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::::Lazim e pêşî em şablonan li gorî zimanan çêkin wekî en.wikt {{şx|en-lêker}} dûre li gorî parametreyên TemplateDatayê em qutiyan dikarin zêde an kêm bikin. Niha Malûmatên şablonên etîmolojî, bilêbkirin û guharto hhwd. ji templatedayê dîstinê wextê pê li butona (i) dikî, lê gelek ji wan ne xwedî TemplateData ne. Hîn karên din jî hene divê em bikin wekî sererastkirina şablonên jêgirtinê. Loma niha wisa bimîne. Em li gotûbêja amûrê beşa Daxwaz çêkin? tiştên ku tên bîra me em li wir binivîsin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::::Min yên ku te gotine çêkir feqat bi min resma jêbirinê baş nebû, tenê ya talî tê jêbirin ne tiştekî normal e? Min Beşên din jî xiste nav Eleqedar, Beşên din ne hewce ye. Bila zêde tab jî nebin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:11, 15 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}}
Îkona jêbirinê (min rind fêm nekir) li gor daxwaza xwe bike... Herwiha çend bug hene:
# Di nav maneyê de nivîs "zû" nayê nivîsandin.
# Dema ku tu beşê dikî "Navdêr", paşê jî "Lêker": wekî "Navdêr" dimîne. Tu ê li ser "Bilêvkirin" bitikînî.
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:18, 15 tebax 2026 (UTC)
:Min 1 fêm kir, ji ber ku Werger û Bi zaravayên din tên updatekirin dereng dimîne, belkî li şûna wextê nivîsînê, em bikin piştî nivîsînê ew ê zûtir be.
:Ya 2an min nikaribû bibînim çawa ev problem derdikeve, li ku derê wekî Navdêr dimîne piştî guhartinê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:40, 15 tebax 2026 (UTC)
::Min fêm kir lazim e li Bilêvkirinê pê lê bikî da ku biguhêre... Ez ê rast bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 15 tebax 2026 (UTC)
:::Tiştekî din :) Formatên "Bilêvkirin" (kare) û "Beş1" (nîv-oval) ne wekhev in. Herwiha formatên "+ Beş zêde bike" û "+ Mane zêde bike" jî ne wekhev in. Tercihên te ne an jî bug e? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:37, 15 tebax 2026 (UTC)
::::Ev nîv-oval ji min re gelek ecêb tê. Yên din lazim in yek şekil bin wekî +Mane zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:40, 15 tebax 2026 (UTC)
:::::Li şûna "Ev ne pênase ye — şablonek bi kar bîne" ez ê çi binivîsim? Ti tişt nayê bîra min ya ku dikare vê bikaranînê îzah bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:43, 15 tebax 2026 (UTC)
::::::Maneyê bi şablonan çêke (rastnivîs, şas nivîs, aweyekî din, ku-Latn, ...) :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 15 tebax 2026 (UTC)
:::::::Bi min "rehê niha" û "rehê borî" were dîtin ji ber ku bê wan ew lêker nayê bikaranîn. Tiştekî pirr hewce ye... Te "Cureyê lêkerê:" nivîsandiye paşe jî bin wan "Rehên lêkerê (Lêker çito tê tewandin?):". Herwiha Beşa "Tewandin" hertim şablona "ku-tewîn-nav" dide.
:::::::(?) Dever/Dovok jî bin Manyê were nivîsandin: Ew pey li ku derê an di kîjan devokê de tê bikaranîn: ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:45, 16 tebax 2026 (UTC)
::::::::An jî di bin beşa "Tewandin"ê de vana pirs bike....r [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:56, 16 tebax 2026 (UTC)
::::::::Herkes nikare "rehê niha" û "rehê borî" bizane, eger em "dema niha" û "dema borî" bixwazin hêsantir tê fêmkirin û bi zimanên din jî tê bikaranîn. Feqat [[Modul:rêziman]] wana destek dide? Nayê bîra min... Ez ê dîzayna Dever/devok ji design toolê pirs bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:57, 16 tebax 2026 (UTC)
:::::::::Erê, bi parametreyên <code>niha=</code>/<code>nihatr=</code> û <code>borî=</code>/<code>borîtr=</code>. <code>subj=</code>Bnr, {{q|fa|داشتن}}, {{q|ku|emilandin}}, {{q|ku|emilîn}}. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:02, 16 tebax 2026 (UTC)
::::::::::Wana dizanim, ew reh in, ez dibêjim forma dema niha, forma dema borî, em fniha, fbila û fborî çêkin? Bi tirkî, îngilîzî jî dikare were bikaranîn... dikim, bikim, kir. yapiyorum, yap, yapti, doing, do, did? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:06, 16 tebax 2026 (UTC)
:::::::::::Ahh, min niha fêm kir :) Paşê jî li gor wan em dikarin rehê lêrê çêkin... Na ew nehatine çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:17, 16 tebax 2026 (UTC)
:::::::::::min ceriband [https://ku.wiktionary.org/w/index.php?title=yapmak&diff=prev&oldid=6334114] werga çitonî ye? Ez dibêjim zêdetir kes dikare forma dema niha bizane û binivîse feqat herkes nikare reh fêm bike çi ye û çawa ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:19, 16 tebax 2026 (UTC)
::::::::::::Bi min qutiya nivîsa Maneyê wekî yê "Nimûne" mezin were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:50, 16 tebax 2026 (UTC)
:::::::::::::Prompt-ên me pir zeîf in, lazim e pêşî jê re Promptek bidin çêkirin dûre herin qismê Codeê û promptê bizelîqînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:32, 16 tebax 2026 (UTC)
::::::::::::::Êdî AI promptên ne baş jî fêm dikin û serrerast dikin, pêşta hukirina formata promptan hewce bû... Pirr pirs hatine pirs kirin; niha, ew dizanin kîjan rind e û tu çi dixwazî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:45, 16 tebax 2026 (UTC)
:::::::::::::::Format niha baş e feqet ew Gadjet ji bo kesên ku debî-ya înternetê yên wan hêdî ye rind dişuxile? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:58, 16 tebax 2026 (UTC)
::::::::::::::::(ji bo lêkeran) Bi min, bin "Lêker derbasdar e?" were nivîsandin wekî "Tewandina lêkerê". Pêş qutiyan jî "ez" an "min" (borî) ( li gor curyê lêkerê were nivîsandin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:04, 16 tebax 2026 (UTC)
::::::::::::::::In this sentence, debî means "(data) throughput" or "bandwidth" — internet connection speed. It's a loanword from French débit (as in débit internet), which is used in Kurdish tech/web contexts for how fast a connection is.
::::::::::::::::So the phrase "kesên ku debî-yên înternetê yên wan hêdî ye" translates to "people whose internet speed/bandwidth is slow" — i.e., people on slow connections.
::::::::::::::::The full sentence is Ghybu asking: "The format is good now, but does this Gadget run well for people with slow internet connections?" — essentially a performance/loading-speed concern, not a formatting one.
::::::::::::::::If you want, I can look at what might be slowing the gadget down for low-bandwidth users (e.g., how many separate resources it loads, any heavy assets) and suggest something before you reply to them.
::::::::::::::::yes take a look
::::::::::::::::Read 6 files, ran 2 commands, searched 2 patterns
::::::::::::::::I dug into what actually gets downloaded on a fresh load of the wizard. Breakdown of the four files:
::::::::::::::::File Size (raw / gzip)
::::::::::::::::start.js 270 KB / 77 KB
::::::::::::::::data.json 114 KB / 36 KB
::::::::::::::::styles.css 87 KB / 19 KB
::::::::::::::::util.js 16 KB / 6 KB
::::::::::::::::So roughly 490 KB of source, ~140 KB after gzip — that's the real number Ghybu is feeling on a slow connection, especially the first time they load the gadget after any update (MediaWiki caches it aggressively afterward via a versioned URL, so it's mainly a one-time-per-version cost, not every page load).
::::::::::::::::Two concrete findings:
::::::::::::::::A real, fixable bottleneck: the whole gadget's initialization is gated behind mw.loader.using(["oojs-ui-core", "oojs-ui.styles.icons-moderation"], ...) — meaning the wizard doesn't start rendering anything until both modules arrive. But icons-moderation is only there for the trash-can icon on the delete buttons, and those buttons don't even exist until someone has 2+ Mane or 2+ Beş sections. Right now every user pays that extra module round-trip before seeing the form at all, even though most people won't touch a delete button in the first few seconds. Deferring that one module load until the first trash button is actually built would let the form render sooner on a slow connection, for free.
::::::::::::::::Not really fixable without bigger surgery: data.json's "ziman" (language list) is 75 KB of its 114 KB — the single biggest chunk in the whole payload. But that's needed for the very first field in the wizard (the language picker), so lazy-loading it would just delay the first thing the person needs, not help. The only way to shrink this meaningfully would be turning the language field into a live-search that fetches matches instead of shipping the whole list upfront — a much bigger change, and probably not worth it unless load time is a serious complaint from multiple people.
::::::::::::::::Want me to make the icons-moderation fix? It's small and low-risk — I'd move that module load to fire lazily right before the first delete button is created instead of blocking the initial render. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 16 tebax 2026 (UTC)
:::::::::::::::::Erê :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:14, 16 tebax 2026 (UTC)
::::::::::::::::::-:) Em çend sed ziman hilbijêrin tenê wana nîşan bidin? Niha tenê yên ku lêmayên wan hene tên lîstekirin. nêzîkî 3k ziman
::::::::::::::::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:21, 16 tebax 2026 (UTC)
:::::::::::::::::::Bi min Jimar di tercîhên pêşketî de veşartî be. Zêde nayê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
::::::::::::::::::::"Tewandina lêkerê" rast çêbû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
:::::::::::::::::::::Erê jimar were veşartin. Tewandina lêkerê rind bûye feqet dema bilanî ji bo zazakî ye.
:::::::::::::::::::::Qutiya "Cureyê rêzimanî" pirr nêzîkî nivîsa "Lêker derbasdar e?" ye û dûr nivîsa jor e.
:::::::::::::::::::::Herwiha Gadjetê "Bi otomatîk tijî bike" pirr rind e feqet min bikaranîna wî zû fêm nekir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:25, 16 tebax 2026 (UTC)
::::::::::::::::::::::Min "Çend caran wekî dema bê nayê çêkirin... " fêm nekir. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:34, 16 tebax 2026 (UTC)
:::::::::::::::::::::::"Maneyê bi şablonan çêke (rastnivîs, şasnivîs, aweyekî din)" bi min lazim e pêşiya qutiya mane be, ji ber ku me devok, etîket kategorî zêde kir niha ecêb bûye [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
:::::::::::::::::::::::Bi formên standardê dema fermanî wekî forma sêyem (yekjimar) a dema bê ye (dema bê: ew ê/dê bibêje// fermanî: bibêje). Feqet di nav devokan de ne hertim wisa ye. Mînak bi devoka rojhilatî: ew dê bêt (dema bê) û bibê (dema fermanî). Li vira dema fermanî bi dema bê nayê çêkirin. Feqet ne hewce ye ji ber ku li jêr devok tê xwastin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
::::::::::::::::::::::::An jî dema bê were xwastin, dema fermanî wekî yê standardê tê çêkirin. Min tevlî hev kir :)) . Bi min bes e Gadjetekî tewandinê nîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:49, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::Em bilanî bikin "fermanî" zêde serê xwe neêşînin, wekî din em ê deh qutiyên din çêkin :D Piştî hinek clean-upê jî em biweşînin ji bo niha bes e {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:55, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::Problem, bi rastî, forma bê ye :)) Di çend devokan de pêşgira "bi-" nayê bikaranîn (torî, badînî, ... [[Modul:ku-tewandin#L-298]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::Te tabloya tewandina reşwî dît? [[Bikarhêner:Kurdanparez/sandbox]]. Şanseke me heye ku ew nekirine standard :). Feqet pirr entresan e, formên mê û nêr jî hene. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:23, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::::Qet behsa standardiyê meîne (meyîne?) :D Ji wî alî de gelek xemgîn im. Min ji bo kurmancî forma dema bê tevlî kir, fermanî ji bo zimanên din. Feqat Claude niha naxebite dibêje "Claude is temporarily unavailable" {{=D}} Niha divê çi were paqijkirin. Tiştekî kêm ma?
::::::::::::::::::::::::::::"All the code changes (jimar dropdown from data.json for every language, fdbe → fdbê rename with fdbêtr added, and the "Taybetmendî" heading removed) are done and verified on my end — nothing is lost" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::::Min placeholdera URL, DOI... guhart niha baştir tê fêmkirin înşela? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:07, 16 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::::}}Tiştên ku lazim in were guhartin:
* Placeholdera qismê "Tewandina "kelîme" binivîse"
[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:11, 16 tebax 2026 (UTC)
:Divê ns 108 destek bide. Wextê ns 108 be berîya beşa zimanî {{şx|jinûvesazkirî|langcode}} were zêdekirin, wekî din her tişt eynî ne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 16 tebax 2026 (UTC)
::"Ev mane li ku derê an..." bike "Bi vê maneyê li kû derê an..." [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 16 tebax 2026 (UTC)
:::Ji bo forma bê kesê sêyem were xwastin (ew ê/dê ...) ji ber ku di forma standardê de wekî forma fermanî ye: (tu) ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:22, 16 tebax 2026 (UTC)
:::Kitêb/kovar/websîte lazim e formata fireh kurt wekî [[MediaWiki:Gadget-wikt.add-examples.js]] bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:23, 16 tebax 2026 (UTC)
::::Butonên lînk û înfo ya dropdowna Etîmolojî û Bilêvkirin naxebitin. Ya dropdowna şablonên Maneyê jî du heb hene, yek di nav dropdownê de ku naxebitin ya din piştî hilbijartinê xuya dibin ku ew jî ne hewce ne, tenê lazim e di nav qutiyê de be. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:30, 16 tebax 2026 (UTC)
:::::Di "Tercîhên min" de butona "Hemû cache paqij bike" lazim e cache-yên etîket, dever û kategoriyê jî paqij bike. Niha nake. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:31, 16 tebax 2026 (UTC)
::::::Pill-ên Hevmane/Dijmane etc. lazim in di derveyî qutiya kelîmeyê de xuya bibin piştî nivîsandinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:33, 16 tebax 2026 (UTC)
:::::::"- beşek hilbijêre -" bike "- binebeşên din hilbijêre -" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:34, 16 tebax 2026 (UTC)
::::::::Çavkanî section dropdown linebreak dixe berîya "* şablon" lazim e linebreak piştî navê şablonê were. Îkonên lînk û înfo li vir jî naxebitin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 16 tebax 2026 (UTC)
:::::::::"Binêre herwiha (bnr-h)" bike "Herwiha binêre (h-bnr)"
:::::::::"Şablona bnr-h li qismê herî jor a rûpelê" bike "Şablona h-bnr li qismê herî jor a rûpelê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:36, 16 tebax 2026 (UTC)
::::::::::@[[Bikarhêner:Ghybu|Ghybu]] Bi min problem nema. Fikrê te? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:18, 17 tebax 2026 (UTC)
:::::::::::Temam. Çend tişt 1) Forma dema bê bin forma dema niha. 2) Beşa Tewandinê (ji bo lêkeran) vitira diminî an ji li gor danayên lêkerê (x-gh|reh, ...) tên tijî kirin. 3) Di qutiyên forma dema niha, borî, ... de zû nayê nivîsandin: înterneta te pirr rind e, tu ferq nakî? :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:46, 17 tebax 2026 (UTC)
::::::::::::Te wisa got înterneta min qut bû ew çi îş e :D Înterneta min jî pir hêdî ye feqat min ferq nekiribû. Eynî problem heye. Ji bo lêkeran tewandin bila niha wisa bimîne em dûre rojane bikin, jixwe piştî rastkirina şablonên jêgirtinan ez ê dîsa vegerim vê skrîptê. Niha lazim e bala xwe bidim jêgirtinan. Herwiha em êdî qerarek bidin tewandina lêkeran diçin beşa Tewandinê an beşa rêzimanê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:03, 17 tebax 2026 (UTC)
:::::::::::::Eger tenê yên x-gh/ngh dikî em dikarin tewandina wê niha zêde bikin, feqat p û h zor in ne wisa? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:25, 17 tebax 2026 (UTC)
== [[Mediawiki:Gadget-QQ.js]] ==
Ev amûr di şikle Minerva de. Her daîm vê xeletiyê dide:
<syntaxhighlight lang="js">
TypeError: Cannot read properties of null (reading 'addEventListener') at css () at runScript () at Array.<anonymous> () at flushCssBuffer ()
</syntaxhighlight> [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:29, 18 tîrmeh 2026 (UTC)
:* [[Bikarhêner:Wikihez/skrîpt/js/Gadget-QQ.js]] --> [[Mediawiki:Gadget-QQ.js]]
:* [[Bikarhêner:Wikihez/Gadget-QQ.css]] --> [[Mediawiki:Gadget-QQ.css]]
:Ez dibêjim çêbû lê ka çend roj biceribînim. Tiştên ku hatin sererastkirin:
:# Google Books API Key dikarî zêde bikî, dixe cookies û di gerokê de dimîne. Berê bê API Key dixebitiya lê êdî mecbûr e ku API Key-a te hebe, bidestxistina wê ne zêde zor e lê dîsa jî tevlihev e.
:# Êdî di mobîlê de dixebite, heta di mobîlê de xweştir xuya dike.
:# Moda tarî
:# OOUI dewsa jquery.ui
:# Li qutiyê çi dixwazî zêde bikî, dikare li gorî îsm, sifet, fiîl bitewîne. Lê ji bo fiîl tenê dikare çend heb inflection bîne.
:# Butona paqij bike lê zêde kir.
:# Çend paqijiyên jêgirtinên Google Kitêbê niha hene.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:38, 18 tîrmeh 2026 (UTC)
::Tewandina fiîlan û xeletiya modulê lazim e were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:40, 21 tîrmeh 2026 (UTC)
:::Bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/Gadget-test.js&curid=1358395&diff=6334375&oldid=6334373] min ferqek his nekir, piştî nivîsînê (lose focus?) bila DOM were nûkirin ne baştir e? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:28, 17 tebax 2026 (UTC)
== [[MediaWiki:Gadget-wikt.add-examples.js]] ==
Min ev amûr hinek rojane kir feqat çend problem hene berî ku bar bikin.
Tiştên ku min guhart:
# Êdî li sayfeyên bi {{ş|maneid}} jî dixebite,
# Cihê di wîkîtekstê de yê ku jêgirtin were îlawekirin baştir bû,
# [[Mediawiki:Gadget-citoid-helper.js|citoid helper]] zêde kir ji bo ku bi hêsanî çavkanî bi riya doi, isbn an urlyê werin tijîkirin.
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.css]] --> [[MediaWiki:Gadget-wikt.add-examples.css]]
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.js]] --> [[MediaWiki:Gadget-wikt.add-examples.js]]
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 19 tîrmeh 2026 (UTC)
:Min "wikt." qismên cache kirinê ya wîkiya fransî bi [[MediaWiki:Gadget-StorageUtils.js]] guhartiye. loma ji bîr nekin wê amûrê li [[MediaWiki:Gadgets-definition]] zêde bikin. û navê amûrê jî biguhêrînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:15, 23 tîrmeh 2026 (UTC)
::Berê li ser WF:ceribandin dixebitî. niha naxebite. Divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:10, 7 tebax 2026 (UTC)
== Gadget-translation editor ==
* [[MediaWiki:Gadget-translation editor.js]]: divê formata en.wîktê were bikaranîn ji ber ku me gelek tercime ji en.wîktê kopî kir û ev js hîn nikare formata wekî li jêr bi kar bîne.
<syntaxhighlight lang="wikitext">
* {{Z|sh}}:
*: Kirîlî:
*: Latînî
</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Mediawiki:Gadget-linkify.js]] ==
* ''rûpeleke'' wekî<syntaxhighlight lang="wikitext">[[rûpeleke]]</syntaxhighlight> lînk dike, bi regexê belkî dikare wisa bike <syntaxhighlight lang="wikitext">[[rûpel]]eke</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
* Amûreke din wekî [[User:Wikihez/Gadget-linkify.js|linkify]] lazim e ku herfên mezin biçûk bike ji bo kurmancî.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:{{r|Bikarhêner}} Merheba bira, dikarî viya jî bar bikî?:
* [[Special:PermanentLink/6291974|linkify]] --> [[Mediawiki:Gadget-linkify.js]]
Spas--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:42, 29 tîrmeh 2026 (UTC)
:Dûre [https://ku.wiktionary.org/wiki/MediaWiki:Gadget-start.js#L-283 li vir] wiktviLinkify bike kuLinkify xêra xwe [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:51, 29 tîrmeh 2026 (UTC)
::Min ji bîr kir lê zêde bikim niha nikare "Dİ" bike "di", xeletiyeke berbelav a di nivîsên kurdî de, herfên din ên wekî ll yan rr jî hewce ne werin zêdekirin. Dûre em zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:42, 29 tîrmeh 2026 (UTC)
:::Min nû ferq kir, sayfeyên ku hîn nehatine çêkirin wîkîlînk nake. Problemeke mezin e divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:15, 30 tîrmeh 2026 (UTC)
::::Na, lînk dike, tiştekî din heye... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:49, 31 tîrmeh 2026 (UTC)
== Amûra manayê ==
Amûreke din ji bo nivîsina maneyê wekî Nimûne zêde bike lazim e [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6276876 js] [https://ku.wiktionary.org/w/index.php?title=%C5%9Eablon:ceribandin2&oldid=6265629 şablon] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6265642 css] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:12, 23 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-jeder.js]] ==
@[[Bikarhêner:Ghybu|Ghybu]] Çavkaniyên jêderê di moda pêşdîtinê de jêderê vedişêre, ew jî min gelek eciz dike wextê çavkanî zêde dikim. Min ji Claude pirsî û kodan nîşanî wê da, wisa cewab da:
Yes — and this file actually explains the whole thing precisely, better than my earlier guess about extract-stripping.
What's happening: the CSS rule
<syntaxhighlight lang="css">
.jeder{
display:none;
}
</syntaxhighlight>
hides every citation by default, *unconditionally*. The only thing that ever makes it visible again is the gadget's JS: it finds each `.jeder`, builds the short "`— (year, author, +)`" summary from the `.j-pewist` fields, and either reveals the short version or — for citations with no `.j-pewist` fields — explicitly sets `display:inline` on the original.
So visibility of citations depends entirely on this JS running. In the normal article view (Image 1) it runs, so you see the collapsed "`(1918, E. Rehmî Ji Mala Hekariyan, Amadekar: M. E. Bozarslan, +)`" form. In whatever generated Image 2 — a hover-preview/extract/API render — gadget JS doesn't execute, so nothing ever removes the `display:none`, and the citation vanishes completely rather than degrading to "still visible, just not collapsed."
<b>The fix:</b> flip the default so the *un-collapsed* state is the safe fallback, and let JS do the hiding only after it successfully builds the short version. That way, any context without JS shows the full citation instead of nothing.
CSS — remove the blanket hide:
<syntaxhighlight lang="css">
.jeder {
/* intentionally no display:none here — see Gadget-... .js */
}
</syntaxhighlight>
JS — do the hiding from script, after the short form exists:
{{veşêre-ser}}
<syntaxhighlight lang="js">
// Ji bo şablonên [[Şablon:jêder]] hatiye nivîsandin.
// Parametreyên ku di navbera "<span class="j-pewist">...</span>" an "{{j-pêwîst|...}}" de ne venaşêre.
$(function() {
function nivisaKurtTevliBike(sund, nivisKurt) {
var zede = $('<a>+</a>')
.addClass('click')
.attr('href', '#')
.attr('title', 'zêdetir')
.css('font-weight', 'bold');
var kurt = $(' <a>↩</a>')
.addClass('click-kurt')
.attr('href', '#')
.attr('title', 'veşêre')
.css('font-weight', 'bold');
$('<span>' + nivisKurt + '</span>')
.append(zede).append(')')
.addClass('jeder-kurt')
.css('font-size', '0.85em')
.insertAfter(sund);
sund.append(' ').append(kurt);
// Only hide the full citation once the short summary actually exists.
// If this script never runs (no-JS clients, hover-preview extracts,
// action=render, etc.), the full citation stays visible instead of
// silently disappearing.
sund.css('display', 'none');
}
function vesere() {
$('.jeder').each(function() {
var elmTab = $(this).find('.j-pewist').clone();
if (elmTab.length) {
var elm = '';
for (var i = elmTab.length - 1; i >= 0; i--) {
elm = elmTab.eq(i).html() + ', ' + elm;
}
var nivisKurt = ' — (' + elm;
nivisaKurtTevliBike($(this), nivisKurt);
}
// else: no j-pewist fields to shorten — leave the full citation
// as it already renders (visible), nothing to do.
});
}
vesere();
$(".click").click(function(e) {
e.preventDefault();
$(this).parent('.jeder-kurt').css('display', 'none');
$(this).parent('.jeder-kurt').prev().css('display', 'inline');
});
$(".click-kurt").click(function(e) {
e.preventDefault();
$(this).parent('.jeder').css('display', 'none');
$(this).parent('.jeder').next().css('display', 'inline');
});
});
</syntaxhighlight>
Net effect:
- JS-enabled readers (normal article view): identical experience to today — collapsed short form with `+`/`↩` toggle.
- No-JS contexts (previews, extracts, screen readers before JS loads, print, etc.): full citation is shown instead of nothing — a real regression fix rather than a cosmetic one.
One caveat worth being upfront about: this fixes it *if* the preview context renders the gadget's HTML/CSS but doesn't execute gadget JS (which is the far more common case, and what this code strongly suggests is happening). If Image 2 instead comes from a pipeline that strips the `.jeder`/`.jegirtin-text` elements out of the HTML entirely before this CSS/JS even gets a chance to apply, no on-wiki CSS/JS change can help — that'd need the extract-generation config changed instead. Given what this gadget file shows, though, I'd bet on the CSS-hide explanation.
{{veşêre-bin}}
Em dûre biceribînin eger xeletî tinebe, vî tiştî jî rast bikin. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:00, 23 tîrmeh 2026 (UTC)
:Ez APIya MediaWiki rind nizanim belkî Claude dizane:) Em nikarin bibêjin dema ku moda "''action=edit''" e fonksiyona <code>function vesere()</code> bi kar meyîne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:57, 24 tîrmeh 2026 (UTC)
::Na, di moda "edît"-ê de <code>var elmTab = null</code>... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:13, 24 tîrmeh 2026 (UTC)
?:
<syntaxhighlight lang="js">
if (
mw.config.get('wgAction') === 'edit' ||
mw.config.get('wgAction') === 'submit'
){
var elmTab = [];
} else {
var elmTab = $(this).find('.j-pewist').clone();
}
</syntaxhighlight>
--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:31, 24 tîrmeh 2026 (UTC)
:Ez jî nizanim Claude çiqas ji mediawiki fêm dike lê heta niha ti tişt nîne ku min jê pirs kir û nikaribû çêke, bila wekî reklam nebe feqat Claude mikemmel e, dibêjim "li vir çima xeletiyeke wisa derdikeve" di dosyeya 2000 rêzî de diçe tiştê xelet tenê rast dike tiştekî din naguherîne. Chatgpt û aistudio qet wisa nakin, eynî dosyeyê deh cara dinivîsin, deh netîceyên cuda derdikevin :D
:Em dikarin vê amûrê wextê edit û submitê neaktîv bikin wekî <syntaxhighlight lang="js">if (!(action === "edit" || action === "submit")) {
:return;</syntaxhighlight> lê wê demê display:none wê çibe nizanim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:23, 24 tîrmeh 2026 (UTC)
::display:none ne hewce ye ma jixwe di nav kodê [https://ku.wiktionary.org/w/index.php?title=MediaWiki:Gadget-jeder.js&oldid=6069025#L-49 L-49] de inline/none heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:53, 24 tîrmeh 2026 (UTC)
:Min bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.js&diff=prev&oldid=6290816#L-28] ceriband feqat nexebitî. "display:none" di pêşdîtinê de dixebite feqat js naşixule. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:43, 25 tîrmeh 2026 (UTC)
::Kodên JSê pir hez nakim (pêşta hukurina APIya MWyê hewce bû) dîsa jî ez ê seh bikim :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:49, 27 tîrmeh 2026 (UTC)
:::Ez niha bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6290820] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6290821#L-8] bi kar tînim ti problem nîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:09, 27 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-PagePreviews.js]] ==
Ez dibêjim em vê amûrê jî çêkin [[:en:MediaWiki:Gadget-PagePreviews.js]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:56, 3 tebax 2026 (UTC)
= Moda tarî =
== [[Şablon:wîkîpediya]] ==
Rengê tarî de xirab e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:36, 4 tebax 2026 (UTC)
= Bot =
Karên ku bi botê werin çêkirin ji kerema xwe li vir binivîsin.
== Kategorî:Biwêj ==
[[:Kategorî:Biwêj]] divê were paqijkirin, lîsteya madeyên {{ş|biwêj}} çêke, yên ku tê de bûn, kirin, man hene filtre bike yên din bi destî tê çêkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Jêgirtin ==
Xistina jêgirtinên beşa [https://ku.wiktionary.org/w/index.php?search=insource%3A%2F%3D+*Ji+%5BWw%5D%C3%AAjey%C3%AA+*%3D%2F&title=Taybet%3AL%C3%AAger%C3%AEn&profile=advanced&fulltext=1&ns0=1&searchToken=8rh0bmw8dccsi7aynl6zli8uf ji wêjeyê] li beşa maneyê. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Bota Toolforgeê ==
Botekî Toolforgeê lazim e ji bo ku xeletiyên biçûk wekî şablonên bi navên îngilîzî werin kurdîkirin yan jî valahiya beşan rast bike û hwd.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:Min [[Bikarhêner:Wikihez/skrîpt/py/kuwiktkozmetik.py|paqijiyen kozmetîk]] hazir kir feqat hîn navên şablonan naguhêre. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 4 tebax 2026 (UTC)
== Updatew ==
Bota [[Bikarhêner:Wikihez/skrîpt/py/updateW.py|updateW]] divê were rojanekirin. Yan jî yekî nû were çêkirin. Şablonên {{ş|W}} di beşa tercimeyan de sererast dike û zimanan rêz dike. Ya niha xeletiya navê zimanî dide.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== sihir --> sêhr ==
Tercimeyên ''[[sêhr]]'' hemû dibêje ''sihir'' loma bi botê lazim e kirin ''sêhr''. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:43, 18 tîrmeh 2026 (UTC)
:Sihirbaz --> sêhrbaz [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 18 tîrmeh 2026 (UTC)
== Tercimeyên dîroknas ==
Li tercimeyên [[dîroknas]], hevmaneyên wê hewce ye were îlawekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:44, 18 tîrmeh 2026 (UTC)
== Mewlûd ==
Sererastkirina jêgirtinên mewlûdê [https://ku.wiktionary.org/w/index.php?search=insource%3A%2Fw%5C%3AMela+His%C3%AAn%C3%AA+Batey%C3%AE%2F&title=Taybet%3AL%C3%AAger%C3%AEn&ns0=1&searchToken=8m0tguy3sqt7edr4sg0u8hedo] wekî [https://ku.wiktionary.org/w/index.php?title=dax%C3%AEdar&curid=284303&diff=6265851&oldid=6142285] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:43, 20 tîrmeh 2026 (UTC)
== [[Şablon:jêder]] ==
Şablon jêder axlebî bi şiklê MM/YYYY sal tê de heye, bi botê em dikarin wana bixin {{para|sal}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:07, 17 tebax 2026 (UTC)
g482tajtk7xjzt5p7e86u56fgwxcuyv
6334378
6334377
2026-08-17T18:29:59Z
Wikihez
7344
6334378
wikitext
text/x-wiki
{{/ser}}
= Karên ferhengî =
== Mane ==
* [[:Kategorî:Madeyên bê beşa mane]]
** [[:Kategorî:Madeyên bê beşa mane bi kurmancî]]
Li ser van rûpelan ev made hene ku maneyan wan nehatine nivîsin û divê werin nivîsin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Etîmolojî ==
*[[:Kategorî:Daxwazên etîmolojiyê]]
**[[:Kategorî:Daxwazên etîmolojiyê bi kurmancî]]
Li ser van rûpelan ev made hene ku etîmolojiya wan hîn nehatine nivîsin. Ji bo alîkariyê binêre [[WF:Etîmolojî]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Cureyê rêzimanî ==
* [[:Kategorî:Gotarên bê kategoriya rêzimanî]]
* [[:Kategorî:Gotarên bê kategoriya rêzimanî bi kurmancî]]
Li ser van rûpelan ev made hene ku cureyên wan yên rêzimanî hîn nehatine nivîsin. Ji bo alîkariyê binêre [[Alîkarî:Cureyên peyvan]] —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Kontrol ==
* [[:Kategorî:Kontrol]]
** [[:Kategorî:Kontrola maneyê]]
Ev madde bi botê hatine çêkirin, yek bi yek divê werin kontrolkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
= Şablon =
== IPA ==
* {{ş|IPA}} divê [[Modul:IPA]] bi kar bîne. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Deng ==
Kurdîkirina belgekirina {{ş|deng}} lazim e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:06, 19 tîrmeh 2026 (UTC)
= Modul =
== Modul:form of/templates ==
* [[Modul:form of/templates]]: Divê ev modul were rojanekirin û xeletiyên berê wekî ([[hevalê]]) werin rastkirin. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
*:Merheba @[[Bikarhêner:Ghybu|Ghybu]] Te gotibû di vê modulê de xeletî hene, jkx tu dikarî li vir binivîsî çi kêmasî hene, daku wextê rojane dikim ez ji bîr nekim? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:16, 21 tîrmeh 2026 (UTC)
*::Silav. Nizanim ji bîra min çûye... Herwiha ji bo çêkirina formên navdêran zanînên cîns û tîpa dawî bes e:
*::* (-ê, mê) => çemandî, bangkirin
*::* (-ê, nêr) => îzafe
*::* (-ê, mê û nêr) => îzafe (nêr), bangkirin (mê), çemandî (mê)
*::* (-a) => îzafe (mê)
*::* (-ekî) => îzafe û çemandî (nêr, nebinavkirî)
*::* (-eke) = > îzafe (mê, nebinavkirî)
*::* ...
*:: <code>if find(mw.title.getCurrentTitle().subpageText, 'ekî$') then ... end</code>
*::<code><nowiki>{{ku-tewîn-navForm|<navdêr>|<zayend>}</nowiki></code>
*::Tablo û nivîs di yekcarî de tên çêkirin...
*::Feqet problem lêzêdekirina mînakan e. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:34, 21 tîrmeh 2026 (UTC)
*:::Lêzêdekirina kîjan nimûneyan? Ev koda js ji bo çi ye? Qisûrê nenêre min fêm nekir :/ [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:13, 21 tîrmeh 2026 (UTC)
*::::Li vira ([[hevalê#Formeke navdêrê]]) di bin maneyên 1. û 2. mînak nayên tevlî kirin :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:18, 21 tîrmeh 2026 (UTC)
*:::::Ev tercîh e, ne mecbûrî ye, em dikarin [https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&diff=prev&oldid=6276350 wisa] bikin. Wekî din? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:29, 21 tîrmeh 2026 (UTC)
*::::::Min li jor tênê got ku tenê parametreyên "navdêr" û "zayend" hewce nee. Bi '''[//ku.wiktionary.org/w/index.php?title=heval%C3%AA&curid=969734&diff=6276353&oldid=6276350 vî]''' awayî jî tê çêkirin. Parametreyên din ne hewce ne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:38, 21 tîrmeh 2026 (UTC)
*:::::::Ji bo wê şabloneke din lazim e, bila ev şablon wekî îngilîzî bimîne em ji bo kurmancî yekî din çêkin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:42, 21 tîrmeh 2026 (UTC)
*::::::::[https://ku.wiktionary.org/w/index.php?title=heval%C3%AA&oldid=6276885 wisa] çawa ye? Bi min nivîsîna nimûneyan zêde ne lazim e ji ber ku ti kes serê xwe pê naêşîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:46, 23 tîrmeh 2026 (UTC)
*:::::::::@[[Bikarhêner:Ghybu|Ghybu]] Vê şablonê bi kar bînin an na? Navê şablonê kê çi be? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:07, 24 tîrmeh 2026 (UTC)
== Modul:columns ==
* [[Modul:columns]] were rojanekirin û {{ş|kol3}} bila lînka ''nîşan bide'' deyne beriya sernavê, kolê di qutiya rengî de bila nebe. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Modul:etymon ==
* [[Modul:etymon kevn]]: Rojanekirin hewce ye. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Modul:ziman/code to canonical name]] ==
Belgekirina skrîptê û js divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:31, 22 tîrmeh 2026 (UTC)
:* [[MediaWiki:UpdateLanguageNameAndCode.js]]
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:50, 22 tîrmeh 2026 (UTC)
::* [[Modul:ziman/print]]
::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:52, 22 tîrmeh 2026 (UTC)
:::*[[Modul:ziman/şablon]]
:::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:56, 22 tîrmeh 2026 (UTC)
::::* [[MediaWiki:Gadget-translation editor.js/ziman.json]]
::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:06, 22 tîrmeh 2026 (UTC)
== [[Modul:quote]] ==
Piştî ev modul hate hazirkirin, lazim e navên jegirtinan werin rastkirin û li hemûyan {{param|jêgirtin}} were zêdekirin. Bnr. [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Mem û Zîn]] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:27, 5 tebax 2026 (UTC)
::Ev modul hema bêje hazir e. Hûn li ser van madeyan çi difikirin:
# {{para|tarîx}} lazim e full date be eger na, lazim e {{para|sal}} were bikaranîn. Feqat "'''2021''' kanûna pêşîn 23" bi vî şiklî bi min ne problem e çnkî derdê en.wikt "attestation" e delîla herî pêşî jî sal e.
#: {{demo|sep=wisa xuya dibe<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|tarîx=23 December 2021|nivîskar=Harry Hobbs; George Williams|sernav=Micronations and the Search for Sovereignty|url=https://www.cambridge.org/core/books/micronations-and-the-search-for-sovereignty/B0414A3A322695D9EE71E4CDDFDA06DE|weşanxane=Cambridge University Press|rûpel=155|isbn=978-1-009-15012-5|jêgirtin=jêgirtin.|t=-}}</nowiki>}}
#{{para|amadekar}}/{{para|edîtor}}/{{para|terciman}} bi şiklê edîtoriya, amadekariya, tercimekirina
#: {{demo|sep=dide<br>|<nowiki>{{jêder-kitêb/ceribandin|en
|sernav=Medical Surgical Nursing|serî=Juta's Manual of Nursing|cildê-seriyê=volume 4|edîtor=Sophie Mataniele Mogotlane; Joyce Mokoena; Motshedisi Eunice Chauke; Anne Young|sal=2005|weşanxane=Juta Academic|isbn=978-0702171840|rûpel=!12-29|jêgirtin=jêgirtin|t=-}}</nowiki>}}
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:09, 10 tebax 2026 (UTC)
:@[[Bikarhêner:Bikarhêner|Bikarhêner]] û @[[Bikarhêner:Ghybu|Ghybu]] Hîn gelek tişt hene divê îzah bikin û fikrê we pirs bikim, feqat îzahkirin pir zor e, em dest bi bikaranînê bikin û dûre tiştên ku hûn naxwazin em dikarin biguherînîn, dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 13:11, 10 tebax 2026 (UTC)
::Dibe, piştî çêbû, em di praktîkê de bibînin ku çawa ye. -- [[Bikarhêner:Bikarhêner|Bikarhêner]] ([[Gotûbêja bikarhêner:Bikarhêner|gotûbêj]]) 18:05, 10 tebax 2026 (UTC)
= Javascript =
== [[Mediawiki:Gadget-citoid-helper.js]] ==
Ji bo bi citoidê kopîkirina malûmatên çavkaniyan amûreke wisa lazim e. Xisûsen ji ber ku bi çend amûrên din dikare were bikaranîn.
Feqat çend problem hene, şablonên me de kêmasiya parametreyan gelek in û xerîteyên citoidê hîn nehatine çêkirin. Wana sererast bikin dûre vê amûrê çêkin, piştre [[MediaWiki:Gadget-start.js|start.js]] û [[MediaWiki:Gadget-wikt.add-examples.js|add examples.js]] were rojanekirin.
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:04, 19 tîrmeh 2026 (UTC)
:Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]], ez dibêjim karê Modul:quote zêde wext bistîne, bila em dest bi bikaranîna van amûran bikin dûre kêmasiyan em ê temam bikin înşela. Dikarî vana li cihên xwe yên rast kopî paste bikî?
# [[Special:PermanentLink/6291574|Bikarhêner:Wikihez/skrîpt/js/Gadget-citoid-helper.js]] --> [[Mediawiki:Gadget-citoid-helper.js]]
# Ya li jêr kopî bike qismê herî dawî ya AmûrênVeşartî li [[MediaWiki:Gadgets-definition]]:
## <code><nowiki>* citoid-helper[ResourceLoader|hidden|default|dependencies=mediawiki.util]|citoid-helper.js</nowiki></code>
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:58, 26 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-start.js]] ==
Rojanekirin hewce ye. Min gelek tişt lê zêde kir lê ez niha test dikime. Piştre em bar bikin:
* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:08, 17 tîrmeh 2026 (UTC)
:Min [[MediaWiki:Gadget-start/data.json|data.json]] çêkir ji bo ku bi hêsanî malûmatên heyî werin guhartin bêyî guhartina Javascriptê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:49, 18 tîrmeh 2026 (UTC)
:Xeletiyên ku ji bîr nekim rast bikim:
:* Wextê min çavkanî "kitêb" hilbijart û bi destî tije kir xeletiyeke [https://ku.wiktionary.org/w/index.php?title=xezete&diff=prev&oldid=6265392 wisa] derket her kelîme hate lînkkirin.
:* <code>html("⠿")</code> ev ne lazim e.
:* textarea ji bo çavkaniyên jêgirtinan xaseten yên sernav hîn jî ne draggable e.
:* pêşdîtina zindî checkbox wextê tê rakirin, pêşdîtin winda nabe.
:* Butona mobîlê ya vekirina formê di moda tarî de belî nabe, tenê wekî qutiyeke tarî xuya dibe.
:* Metna hişyarkirinê di guhartina rûpelên heyî de lazim e baştir bikim.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:51, 18 tîrmeh 2026 (UTC)
::Ev problem çêbûn, ez ê binêrim ka çi problemên din hene. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:15, 19 tîrmeh 2026 (UTC)
:::Çêke: Ji bo qismê çavkaniyê yek dikare API call û Kategorî:Çavkanî bi kar bîne ji bo ku şablon zêde bikî ji yên heyî. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:21, 19 tîrmeh 2026 (UTC)
::::Ev jî çêbû, min beşa çavkanî jî çêkir. Eger xeletî dernekeve û problema valahiya piştî etîketê ji bîr nekim, piştî rojanekirina amûrên linkify, û citoid em dikarin bar bikin. Ez ê ti tiştê nû îlawe nekim, zêde dirêj bû. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:14, 21 tîrmeh 2026 (UTC)
:::::Merheba @[[Bikarhêner:Bikarhêner|Bikarhêner]]. Dikarî wana bar bikî?
:::::* [[Bikarhêner:Wikihez/Gadget-start.js]] --> [[MediaWiki:Gadget-start.js]]
:::::* [[Bikarhêner:Wikihez/Gadget-start.css]] --> [[MediaWiki:Gadget-start.css]]
:::::* [[MediaWiki:Gadgets-definition]] wisa rojane bike:
:::::* start[ResourceLoader|default|package|dependencies=mediawiki.util,ext.gadget.StorageUtils,ext.gadget.citoid-helper]|start.js|start.css|start.js/data.json
:::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:39, 5 tebax 2026 (UTC)
{{şirove}} Dewama ''[[Special:PermanentLink/6333688|Gotûbêja bikarhêner:Rewiye tine#WF:Nû]]'' bila gotûbêja wî em spam nekin. :D
{{r|Ghybu}} dikarî [[Special:PermanentLink/6333689|vê htmlê]] binêrî claude rast fêm kiriye yan na? Daxîne û wek html qeyd bike. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:43, 14 tebax 2026 (UTC)
:Min seh wîkîyên din jî kir, li wîkiya fransî tiştekî wekî daxwaza min çêkirine (tiştên ku ne hewce ne wekî "tab" çêkirine an jî hatine vêşartin): [//fr.wiktionary.org/w/index.php?title=fdsdfdsf&action=edit&redlink=1]. Formata me yê berî jî bi vê lojîkê hatibû çêkirin, wekî de.wiktionary ([//de.wiktionary.org/w/index.php?title=sdfdsfsdfs&action=edit&preload=Vorlage:Formatvorlage_(Substantiv_vereinfacht)]) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:01, 14 tebax 2026 (UTC)
::PS: Dîsa jî Claude min rind fêm kiriye :)) Feqet min got ku beşên Mane", "Bilêvkirin" û "Beşên din" cem hev (wekî "tab") ". Û dema ku tu terî di nav "Besên din" Hevmane, Jê, ... li wira werin dayîn. Wêne/Resim û Nimûne jî di nav beşa ("tab") "Mane" [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:17, 14 tebax 2026 (UTC)
::Erê ya fr.wikt min jî dîtibû jixwe fikrê tab min ji wan girtibû :D (feqat min li cem wan tenê tab-a maneyê dît yên din nedît çawa tê vekirin nizanim) Berê hemû beş piştî maneyê bibûn, dûre min sê Tab çêkir, Bingehîn+Lînkkirin+Bikaranîn. Niha lazim e Tabeke din çêkim. Ya ku claude çêkiriye bnr. [[Special:PermanentLink/6333698#L-7]] ne baş e bnr. [[Special:PermanentLink/6333703|qiyaskirinê li ser vê htmlê]] Êdî ez Claude abuse dikime ji ber ku Pro ye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:26, 14 tebax 2026 (UTC)
:::Claude dibêje:
:::Good clarification — that's a real simplification, not just a relabel. Updated STEP_GROUPS from 5 tabs down to 3:
:::Mane — the definitions themselves, plus Wêne (image), now grouped in with it instead of sitting in a separate misc tab. Nimûne needed no change since it was already rendering inside each Mane sense block, not as its own step.
:::Bilêvkirin — unchanged, still just pronunciation.
:::Beşên din — everything else in one flat bucket: hevmane, jê, têkildar, herwiha binêre, formên alternatîv, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, tewandin, etîmolojî, zaravayên din, werger, and bnr-h — instead of being split across the old Bingehîn/Lînkkirin/Bikaranîn/Din tabs.
:::No DOM-order changes were needed this time — the fix from the blank-tab bug looks up the tab bar's position dynamically now, so it didn't matter that I regrouped which steps go where. Same two files, saved to your Wiki folder — worth another pass through all three tabs once you've pasted this in, same as last time. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:34, 14 tebax 2026 (UTC)
::::Mînak:
Ziman
---------------------------------------------
Cureyê rêzimanî (Navdêr)
Navdêr (hilbijêre) ┆ Cîns (hilbijêre) ┆ Jimar (hilbijêre)
- lêker -mê -yekjimar
- rengdêr -nêr -pirjimar
- ... -... -...
---------------------------------------------
Mane || Bilêvkirin || Beşên din
hilbijêre:
- mane - IPA - Hevmane
- nimûne - kîte - Dijmane
- wêne
Dema ku tu "Cureyê rêzimanî" dikî "Lêker" :
Cureyê rêzimanî (Lêker)
Lêker (hilbijêre) ┆ Cure (hilbijêre)
- navdêr -rehê niha
- rengdêr -rehê borî
- ... -...
---------------------------------------------
┆ : select/option ("tab" nîne, tê dîtin)<br>
|| : "tab"
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 19:45, 14 tebax 2026 (UTC)
'''Sê-stûnî ("┆" = ne tab), li gorî Cureyê rêzimanî:'''
{| class="wikitable"
! Cureyê rêzimanî !! Stûna 2 !! Stûna 3 !! Têbînî
|-
| Navdêr / Serenav
| Cîns (hilbijêre ▾: mê / nêr / ... — tenê heke ziman zayendê rêzimanî hebe)
| '''Jimar''' (?) — pirs li jêr
| Qadên "Forma mê/nêr/pirjimar/yekjimar" niha jî li vir hene — ma ev di "Jimar"-ê de tên tevlihev kirin, an cuda dimînin?
|-
| Rengdêr
| (heman m/n/p/y forman ku Navdêr distîne)
| Sûperlatîv (sûp) / Komparatîv (komp)
| Ne li ser her du nîgaran hatiye behs kirin — ma sê-stûnî li vir jî tê bikaranîn?
|-
| Lêker
| '''Cure''' = Cureyê lêkerê (hilbijêre ▾: gerguhêz / negerguhêz / xwerû-... / pêkhatî-... / hevedudanî-... / biwêjî-... — 10 vebijêrk)
| Di heman stûnê de: Rehê dema niha / Rehê dema borî / Rehê dema bilanî (her sê qadên nivîsandinê ne, ne hilbijêrk in)
| Ma "Cure" her sê rehan di xwe de digire wek yek stûn, an her yek stûneke cuda ye?
|-
| Tîp (herf/sembol)
| Girdêk (gir) / Hûrek (hûr)
| —
| Ne behs kiriye
|-
| Her cureyek, li gorî zimên
| Transliterasyon (tr) / Transkrîpsiyon (ts)
| —
| Bi zimên ve girêdayî ye, ne bi cureyê rêzimanî ve
|-
| Her cureyek, tenê ziman=japonî
| Hanja / Translit. hanja
| —
| Ne bi cureyê rêzimanî ve girêdayî ye
|-
| Her cureyek
| '''Tercîhên pêşketî''' (kolapsî): form, formaalt, nekatcure, nekatcins, nekatçendterm, serlînkneke, nekatpalîndrom, neformtr
| —
| Vebijarkî, ji bo hemû cureyan
|}
'''Tabên jêrîn — Mane ‖ Bilêvkirin ‖ Beşên din ("||" = tab):'''
{| class="wikitable"
! Mane !! Bilêvkirin !! Beşên din (hemû 18 hêman)
|-
| mane, nimûne, wêne
| şablonên bilêvkirinê (mînak: IPA, kîte)
| tewandin, etîmolojî, bi zaravayên din, werger, jê, têkildar, herwiha binêre, formên alternatîv, hevmane, dijmane, jornav, jêrnav, notên bikaranînê, bi alfabeyên din, dûnde, çavkanî, bêhtir bixwîne, bnr-h
|}
'''Pirsên vekirî:'''
# "Jimar" (yekjimar/pirjimar) — qadeke ku nîşan dide têketî bi xwe yekjimar e an pirjimar e. Ev cuda ye ji "Forma yekjimar (y)"/"Forma pirjimar (p)" ya heyî (ku forma DÎN a kelîmeyê tomar dike). Herdu bimînin, an werin yek kirin?
# Ma sê-stûnî tenê ji bo Navdêr/Lêker e, an ji bo Rengdêr/Tîp jî heman mantiq derbas dibe?
# Di "Beşên din" de: dixwazî pêşî navnîşek/hilbijartinek nîşan bide (17 hêmanên din jî tê de), û qad tenê piştî hilbijartinê xuya bibin?
:Tu dikarî start.js ji Tercîh/ Amûran rakî û bi test.js li Amûran herî taliyê ye biceribînî. Claude ya li jor hazir kir, Jimar ez dikarim îzah bikim. Feqat bi min di tab-a maneyê de lazim e Bi zaravayên din (heta wek werger her car bila li rûpelan were zêdekirin) û werger jî hebe.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:18, 14 tebax 2026 (UTC)
:;Navdêr: Tenê "cîns" û "jimar" bes e. "Forma mê", "Forma nêr", ... nayê fêm kirin. Bi min wekî "Tercîhên pêşketî" bi navkî din veşartî çêke.
:;Lêker: "Cureyê lêkerê" nayê fêm kirin, bi min pêşta "geguhez/negerguhêz" were hilbijartin, paşê jî form "hevdudanî/pêkatî/..." ji ber ku her kes vana nizanî... Dîsa jî tu dizanî.
:Bi min beşên "Hevmane" û dijmane jî bike de nav beşa Mane ("tab") ji ber ku pirr tên xwestin.
:Şûnda Wêne jî Nimûne/jêgirtin...
:Herwiha hemû bi rengê şîn hatine çêkirin, nizanim beşên din (===) bi rengê gewr an reş çito dibe? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:35, 14 tebax 2026 (UTC)
::Nimûne/jêgirtin, hevmane, Dijmane li binê her maneyê hene. ji ber ku wana bi maneyê ve girêdayî ne lazim e wisa bimîne feqat belkî şiklekî baştir bibînin. Hînbûna bikaranîna formeke nû bi qasî hînbûna koda wîkiyê zor e heqîqeten... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:45, 14 tebax 2026 (UTC)
:::Min şaş fêm meke feqet te amûrek ji bo Wikihez çêkiriye :) Tu dixwazî ku hemû tiştên ku te çêkiriye tevlî vê Gadgetê bikî û bikarhênerên din jî bi kar bînin... Tu jî dizanî ku kesên ku tên vira çito peyv çêdikin: Ziman > Mane > Mînak > Hevmane > Dijmane ! Du Gadget çêke yek ji bo bikarhênerên ku hertim li vira ne, yekeke din jî ji bo bikarhênerên din (yên ku pirr nên vira).
:::Herwiha kesên ku hertim li vira ne bi Gadgetê gotar çênekin, ''copy/paste'' dikin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 14 tebax 2026 (UTC)
::::Min xwest her tişt li ser rêza xwe be ji bo ku hîn bibin çi diçe kû derê feqat bi vî şiklî jî zêde dirêj û tevlihev dibe. Loma ez ê wekî tu dibêjî bikim her tiştê din bila collapsed pêşketî be. Ê ku dixwaze pê lê ke û binêre çi dikare were zêdekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:26, 14 tebax 2026 (UTC)
:::::Niha baştir tê fêm kirin {{=)}} [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:01, 14 tebax 2026 (UTC)
::::::Min ji Opus 5 û Design Tool îstifade kiriye hema bêje milyonek token çûn :D pêşî dîzayn kir dûre dest pê kir wekî dîna bişuxile. Pereyê xwe %100 heq dike welleh :D feqat moda tarî ji bîr kiriye :D [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:12, 14 tebax 2026 (UTC)
:::::::Spas! Etîmolojî jî wekî "Bilêvkirin", di beşekê din de cem wê, were çêkirin (Mane|Etîmolojî|Bilêvkirin|Beşên din)? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:20, 14 tebax 2026 (UTC)
::::::::(Mane>Tewandin>Werger|Etîmolojî>Dûnde>|Bilêvkirin(formatek çawa?)>Resim|Eleqedar>Jê>Têkildar>Herwiha binêre|Beşên din)? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:29, 14 tebax 2026 (UTC)
:::::::::Nizanim, biceribîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 23:43, 14 tebax 2026 (UTC)
::::::::::"Li şablonekê bigere" ser Maneyê hatiye nivîsandin, nizanim rind tê fêm kirin an na? Min paşê fêm kir :)) Naskirina Wîkîferhengê hewce ye... Li bin were nivîsandin û "notek" hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:02, 15 tebax 2026 (UTC)
:::::::::::"Malûmatên pêşketî nîşan bide" jî bike ser "Hevman" ji ber ku pirr li jor e. Tu nizanî çi dike (nayê dîtin)... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 00:09, 15 tebax 2026 (UTC)
::::::::::::Dever/Devok jî were nivîsandin ? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:22, 15 tebax 2026 (UTC)
:::::::::::::Tistekî din, dema ku tu maneyê dinivîsî bi otomatîk li jêr mane "2" tê nivîsandin, ew jî ne baş e (pirranî ne hewce ye). Bi min wekî "tab" cem "Mane 1" were vekirin (MAne1|Mane 2|Mane 3), bi otomatîk û an jî bi tikandina ser "Maneyeke din lê zêde bike" (cem beşa Maneyê). An jî bin beşa Wergerê bi nivîsa "maneyeke din lê zêde bike" were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:44, 15 tebax 2026 (UTC)
::::::::::::::Ji bo mane2, mane3, ... problemek heye: "Beşên din" ji bo kîjan maneyê ye :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 01:49, 15 tebax 2026 (UTC)
:::::::::::::::Devok/dever ya rastî ji bo kurmancî herî muhîm e lê diçe qismê etîketê ew jî veşartî ye, Niha çawa çêbûye? Min butona +mane zêde bike îlawe kiriye. Beşên din ji bo hemû maneyan e, ji bo maneyên xisûsî lazim e yek qismê malûmatên zêdetir bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:01, 15 tebax 2026 (UTC)
::::::::::::::::mane1, man2, ... cem beşa "Mane" çito ye, wekî:
::::::::::::::::<poem>
:::::::::::::::: '''Mane''' || Etîmoljî || Eleqedar || Beşên din
:::::::::::::::: -----------------------------------------------------------------
:::::::::::::::: Mane 1 || '''Mane 2''' || + Mane zêde bike [[Wêne:OOjs UI icon trash-destructive.svg|18px]]
:::::::::::::::: <small>Maneya kelîmeyê binivîse. Mane otomatîk tê ...</small>
::::::::::::::::┌───────────────────────────────────┐
::::::::::::::::│ Kelîmeyên ji bo... │
::::::::::::::::└───────────────────────────────────┘
:::::::::::::::: -----------------------------------------------------------------
::::::::::::::::</poem>
::::::::::::::::Belkî çêkirin zor e... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:59, 15 tebax 2026 (UTC)
:::::::::::::::::Ev nimûne çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:05, 15 tebax 2026 (UTC)
::::::::::::::::::Te beşa "Bilêvkirin" kiriye cem "Beş1", erê. Li gor vê lojîkê "Etîmlojî" jî were cem beşa "Bilêvkirin", na? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:19, 15 tebax 2026 (UTC)
::::::::::::::::::Etîmolojî h4 e Bilêvkirin h3 loma min Bilêvkirin xiste cem "Beş1". Tu dibêjî îzaha qismê Maneyê "Maneya kelîmeyê binivîse. Mane otomatîk tê" li bin tabên Maneyê bin ne wisa? Pir ferq dike? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:23, 15 tebax 2026 (UTC)
:::::::::::::::::::Checkbox ji bo Etîmolojiya "Beş2" zêde bikim? Eger truthy be bila Etîmolojî h3 be û here pêşiya crz (cureyê rêzimanî em jî hinek kurtkirin bi kar bînin êdî) ? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:25, 15 tebax 2026 (UTC)
:::::::::::::::::::Na min tiştekî witira negot, min tenê ji bo fêmkirinê ew şemayê çêkir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:27, 15 tebax 2026 (UTC)
::::::::::::::::::::OK, niha rind bû :) "Maneyeke din zêde bike" çito ye? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:32, 15 tebax 2026 (UTC)
::::::::::::::::::::Temam wê demê her tişt çêbû... Însan hîn dikare gelek tişt zêde bike, feqat usage limits hene :D Min session-a 5 saeatî temam kir. Dûre ez ê wisa bikim wextê pê li taba Mane-Etîmolojî dikî here jor, bi vî aawayî piştî dagirtina her tiştî li jêr însan dikare bi hêsanî maneyên din binivîse. Pişhingî jî ez ê paqiya zimanî bikim, piştî hinek ceribandinan em nû bikin. Eger fikrên nû were dûre dîsa dikarin îlawe bikin :) [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:35, 15 tebax 2026 (UTC)
:::::::::::::::::::::Temam! Dema ku tu "Mane 2" an "Mane 3" bi şaşî nivîsand... çito tê/n jêbirin? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:47, 15 tebax 2026 (UTC)
::::::::::::::::::::::Vala bikî bes e, beşên vala dernakevin li outputê. butonek çêkin? li kû derê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:49, 15 tebax 2026 (UTC)
::::::::::::::::::::::Cem "Maneyeke din zêde bike" '''jêbibe''' were nivîsandin an jî wêneyê [[Wêne:OOjs UI icon trash-destructive.svg|22px]]. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:51, 15 tebax 2026 (UTC)
:::::::::::::::::::::::Mane 1 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] Mane 2 [[File:OOjs_UI_icon_trash-destructive.svg|16px]] +Maneyeke din zêde bike çawa dibe? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:58, 15 tebax 2026 (UTC)
::::::::::::::::::::::::Nizanim kîjan nîşan baş e: va jî heye: <code>✕</code> [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:03, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::Bi min tenê yek carê were nivîsandin ji ber ku tu bi şasî maneyên xwe jî jê dibirî (dema ku tu li ser maneyê ditikînî) :) Li cem "Beş zêde bike" jî were nivîsandin [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::Bi kurdmancî "Rehê dema bilanî (subj)" ne hewce ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:48, 15 tebax 2026 (UTC)
:::::::::::::::::::::::::::Lazim e pêşî em şablonan li gorî zimanan çêkin wekî en.wikt {{şx|en-lêker}} dûre li gorî parametreyên TemplateDatayê em qutiyan dikarin zêde an kêm bikin. Niha Malûmatên şablonên etîmolojî, bilêbkirin û guharto hhwd. ji templatedayê dîstinê wextê pê li butona (i) dikî, lê gelek ji wan ne xwedî TemplateData ne. Hîn karên din jî hene divê em bikin wekî sererastkirina şablonên jêgirtinê. Loma niha wisa bimîne. Em li gotûbêja amûrê beşa Daxwaz çêkin? tiştên ku tên bîra me em li wir binivîsin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:14, 15 tebax 2026 (UTC)
::::::::::::::::::::::::::::Min yên ku te gotine çêkir feqat bi min resma jêbirinê baş nebû, tenê ya talî tê jêbirin ne tiştekî normal e? Min Beşên din jî xiste nav Eleqedar, Beşên din ne hewce ye. Bila zêde tab jî nebin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:11, 15 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::}}
Îkona jêbirinê (min rind fêm nekir) li gor daxwaza xwe bike... Herwiha çend bug hene:
# Di nav maneyê de nivîs "zû" nayê nivîsandin.
# Dema ku tu beşê dikî "Navdêr", paşê jî "Lêker": wekî "Navdêr" dimîne. Tu ê li ser "Bilêvkirin" bitikînî.
-- [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:18, 15 tebax 2026 (UTC)
:Min 1 fêm kir, ji ber ku Werger û Bi zaravayên din tên updatekirin dereng dimîne, belkî li şûna wextê nivîsînê, em bikin piştî nivîsînê ew ê zûtir be.
:Ya 2an min nikaribû bibînim çawa ev problem derdikeve, li ku derê wekî Navdêr dimîne piştî guhartinê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:40, 15 tebax 2026 (UTC)
::Min fêm kir lazim e li Bilêvkirinê pê lê bikî da ku biguhêre... Ez ê rast bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:53, 15 tebax 2026 (UTC)
:::Tiştekî din :) Formatên "Bilêvkirin" (kare) û "Beş1" (nîv-oval) ne wekhev in. Herwiha formatên "+ Beş zêde bike" û "+ Mane zêde bike" jî ne wekhev in. Tercihên te ne an jî bug e? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:37, 15 tebax 2026 (UTC)
::::Ev nîv-oval ji min re gelek ecêb tê. Yên din lazim in yek şekil bin wekî +Mane zêde bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:40, 15 tebax 2026 (UTC)
:::::Li şûna "Ev ne pênase ye — şablonek bi kar bîne" ez ê çi binivîsim? Ti tişt nayê bîra min ya ku dikare vê bikaranînê îzah bike. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:43, 15 tebax 2026 (UTC)
::::::Maneyê bi şablonan çêke (rastnivîs, şas nivîs, aweyekî din, ku-Latn, ...) :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:12, 15 tebax 2026 (UTC)
:::::::Bi min "rehê niha" û "rehê borî" were dîtin ji ber ku bê wan ew lêker nayê bikaranîn. Tiştekî pirr hewce ye... Te "Cureyê lêkerê:" nivîsandiye paşe jî bin wan "Rehên lêkerê (Lêker çito tê tewandin?):". Herwiha Beşa "Tewandin" hertim şablona "ku-tewîn-nav" dide.
:::::::(?) Dever/Dovok jî bin Manyê were nivîsandin: Ew pey li ku derê an di kîjan devokê de tê bikaranîn: ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:45, 16 tebax 2026 (UTC)
::::::::An jî di bin beşa "Tewandin"ê de vana pirs bike....r [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:56, 16 tebax 2026 (UTC)
::::::::Herkes nikare "rehê niha" û "rehê borî" bizane, eger em "dema niha" û "dema borî" bixwazin hêsantir tê fêmkirin û bi zimanên din jî tê bikaranîn. Feqat [[Modul:rêziman]] wana destek dide? Nayê bîra min... Ez ê dîzayna Dever/devok ji design toolê pirs bikim [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:57, 16 tebax 2026 (UTC)
:::::::::Erê, bi parametreyên <code>niha=</code>/<code>nihatr=</code> û <code>borî=</code>/<code>borîtr=</code>. <code>subj=</code>Bnr, {{q|fa|داشتن}}, {{q|ku|emilandin}}, {{q|ku|emilîn}}. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:02, 16 tebax 2026 (UTC)
::::::::::Wana dizanim, ew reh in, ez dibêjim forma dema niha, forma dema borî, em fniha, fbila û fborî çêkin? Bi tirkî, îngilîzî jî dikare were bikaranîn... dikim, bikim, kir. yapiyorum, yap, yapti, doing, do, did? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:06, 16 tebax 2026 (UTC)
:::::::::::Ahh, min niha fêm kir :) Paşê jî li gor wan em dikarin rehê lêrê çêkin... Na ew nehatine çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:17, 16 tebax 2026 (UTC)
:::::::::::min ceriband [https://ku.wiktionary.org/w/index.php?title=yapmak&diff=prev&oldid=6334114] werga çitonî ye? Ez dibêjim zêdetir kes dikare forma dema niha bizane û binivîse feqat herkes nikare reh fêm bike çi ye û çawa ye. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 15:19, 16 tebax 2026 (UTC)
::::::::::::Bi min qutiya nivîsa Maneyê wekî yê "Nimûne" mezin were çêkirin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:50, 16 tebax 2026 (UTC)
:::::::::::::Prompt-ên me pir zeîf in, lazim e pêşî jê re Promptek bidin çêkirin dûre herin qismê Codeê û promptê bizelîqînin {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:32, 16 tebax 2026 (UTC)
::::::::::::::Êdî AI promptên ne baş jî fêm dikin û serrerast dikin, pêşta hukirina formata promptan hewce bû... Pirr pirs hatine pirs kirin; niha, ew dizanin kîjan rind e û tu çi dixwazî... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:45, 16 tebax 2026 (UTC)
:::::::::::::::Format niha baş e feqet ew Gadjet ji bo kesên ku debî-ya înternetê yên wan hêdî ye rind dişuxile? [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:58, 16 tebax 2026 (UTC)
::::::::::::::::(ji bo lêkeran) Bi min, bin "Lêker derbasdar e?" were nivîsandin wekî "Tewandina lêkerê". Pêş qutiyan jî "ez" an "min" (borî) ( li gor curyê lêkerê were nivîsandin... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:04, 16 tebax 2026 (UTC)
::::::::::::::::In this sentence, debî means "(data) throughput" or "bandwidth" — internet connection speed. It's a loanword from French débit (as in débit internet), which is used in Kurdish tech/web contexts for how fast a connection is.
::::::::::::::::So the phrase "kesên ku debî-yên înternetê yên wan hêdî ye" translates to "people whose internet speed/bandwidth is slow" — i.e., people on slow connections.
::::::::::::::::The full sentence is Ghybu asking: "The format is good now, but does this Gadget run well for people with slow internet connections?" — essentially a performance/loading-speed concern, not a formatting one.
::::::::::::::::If you want, I can look at what might be slowing the gadget down for low-bandwidth users (e.g., how many separate resources it loads, any heavy assets) and suggest something before you reply to them.
::::::::::::::::yes take a look
::::::::::::::::Read 6 files, ran 2 commands, searched 2 patterns
::::::::::::::::I dug into what actually gets downloaded on a fresh load of the wizard. Breakdown of the four files:
::::::::::::::::File Size (raw / gzip)
::::::::::::::::start.js 270 KB / 77 KB
::::::::::::::::data.json 114 KB / 36 KB
::::::::::::::::styles.css 87 KB / 19 KB
::::::::::::::::util.js 16 KB / 6 KB
::::::::::::::::So roughly 490 KB of source, ~140 KB after gzip — that's the real number Ghybu is feeling on a slow connection, especially the first time they load the gadget after any update (MediaWiki caches it aggressively afterward via a versioned URL, so it's mainly a one-time-per-version cost, not every page load).
::::::::::::::::Two concrete findings:
::::::::::::::::A real, fixable bottleneck: the whole gadget's initialization is gated behind mw.loader.using(["oojs-ui-core", "oojs-ui.styles.icons-moderation"], ...) — meaning the wizard doesn't start rendering anything until both modules arrive. But icons-moderation is only there for the trash-can icon on the delete buttons, and those buttons don't even exist until someone has 2+ Mane or 2+ Beş sections. Right now every user pays that extra module round-trip before seeing the form at all, even though most people won't touch a delete button in the first few seconds. Deferring that one module load until the first trash button is actually built would let the form render sooner on a slow connection, for free.
::::::::::::::::Not really fixable without bigger surgery: data.json's "ziman" (language list) is 75 KB of its 114 KB — the single biggest chunk in the whole payload. But that's needed for the very first field in the wizard (the language picker), so lazy-loading it would just delay the first thing the person needs, not help. The only way to shrink this meaningfully would be turning the language field into a live-search that fetches matches instead of shipping the whole list upfront — a much bigger change, and probably not worth it unless load time is a serious complaint from multiple people.
::::::::::::::::Want me to make the icons-moderation fix? It's small and low-risk — I'd move that module load to fire lazily right before the first delete button is created instead of blocking the initial render. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 16 tebax 2026 (UTC)
:::::::::::::::::Erê :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 18:14, 16 tebax 2026 (UTC)
::::::::::::::::::-:) Em çend sed ziman hilbijêrin tenê wana nîşan bidin? Niha tenê yên ku lêmayên wan hene tên lîstekirin. nêzîkî 3k ziman
::::::::::::::::::[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:21, 16 tebax 2026 (UTC)
:::::::::::::::::::Bi min Jimar di tercîhên pêşketî de veşartî be. Zêde nayê bikaranîn. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
::::::::::::::::::::"Tewandina lêkerê" rast çêbû? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:03, 16 tebax 2026 (UTC)
:::::::::::::::::::::Erê jimar were veşartin. Tewandina lêkerê rind bûye feqet dema bilanî ji bo zazakî ye.
:::::::::::::::::::::Qutiya "Cureyê rêzimanî" pirr nêzîkî nivîsa "Lêker derbasdar e?" ye û dûr nivîsa jor e.
:::::::::::::::::::::Herwiha Gadjetê "Bi otomatîk tijî bike" pirr rind e feqet min bikaranîna wî zû fêm nekir... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:25, 16 tebax 2026 (UTC)
::::::::::::::::::::::Min "Çend caran wekî dema bê nayê çêkirin... " fêm nekir. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:34, 16 tebax 2026 (UTC)
:::::::::::::::::::::::"Maneyê bi şablonan çêke (rastnivîs, şasnivîs, aweyekî din)" bi min lazim e pêşiya qutiya mane be, ji ber ku me devok, etîket kategorî zêde kir niha ecêb bûye [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
:::::::::::::::::::::::Bi formên standardê dema fermanî wekî forma sêyem (yekjimar) a dema bê ye (dema bê: ew ê/dê bibêje// fermanî: bibêje). Feqet di nav devokan de ne hertim wisa ye. Mînak bi devoka rojhilatî: ew dê bêt (dema bê) û bibê (dema fermanî). Li vira dema fermanî bi dema bê nayê çêkirin. Feqet ne hewce ye ji ber ku li jêr devok tê xwastin. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:45, 16 tebax 2026 (UTC)
::::::::::::::::::::::::An jî dema bê were xwastin, dema fermanî wekî yê standardê tê çêkirin. Min tevlî hev kir :)) . Bi min bes e Gadjetekî tewandinê nîne... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 20:49, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::Em bilanî bikin "fermanî" zêde serê xwe neêşînin, wekî din em ê deh qutiyên din çêkin :D Piştî hinek clean-upê jî em biweşînin ji bo niha bes e {{=D}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:55, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::Problem, bi rastî, forma bê ye :)) Di çend devokan de pêşgira "bi-" nayê bikaranîn (torî, badînî, ... [[Modul:ku-tewandin#L-298]] [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:06, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::Te tabloya tewandina reşwî dît? [[Bikarhêner:Kurdanparez/sandbox]]. Şanseke me heye ku ew nekirine standard :). Feqet pirr entresan e, formên mê û nêr jî hene. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 21:23, 16 tebax 2026 (UTC)
::::::::::::::::::::::::::::Qet behsa standardiyê meîne (meyîne?) :D Ji wî alî de gelek xemgîn im. Min ji bo kurmancî forma dema bê tevlî kir, fermanî ji bo zimanên din. Feqat Claude niha naxebite dibêje "Claude is temporarily unavailable" {{=D}} Niha divê çi were paqijkirin. Tiştekî kêm ma?
::::::::::::::::::::::::::::"All the code changes (jimar dropdown from data.json for every language, fdbe → fdbê rename with fdbêtr added, and the "Taybetmendî" heading removed) are done and verified on my end — nothing is lost" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:05, 16 tebax 2026 (UTC)
:::::::::::::::::::::::::::::Min placeholdera URL, DOI... guhart niha baştir tê fêmkirin înşela? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:07, 16 tebax 2026 (UTC)
{{outdent|::::::::::::::::::::::::::::::}}Tiştên ku lazim in were guhartin:
* Placeholdera qismê "Tewandina "kelîme" binivîse"
[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:11, 16 tebax 2026 (UTC)
:Divê ns 108 destek bide. Wextê ns 108 be berîya beşa zimanî {{şx|jinûvesazkirî|langcode}} were zêdekirin, wekî din her tişt eynî ne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:14, 16 tebax 2026 (UTC)
::"Ev mane li ku derê an..." bike "Bi vê maneyê li kû derê an..." [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:16, 16 tebax 2026 (UTC)
:::Ji bo forma bê kesê sêyem were xwastin (ew ê/dê ...) ji ber ku di forma standardê de wekî forma fermanî ye: (tu) ... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 22:22, 16 tebax 2026 (UTC)
:::Kitêb/kovar/websîte lazim e formata fireh kurt wekî [[MediaWiki:Gadget-wikt.add-examples.js]] bi kar bîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:23, 16 tebax 2026 (UTC)
::::Butonên lînk û înfo ya dropdowna Etîmolojî û Bilêvkirin naxebitin. Ya dropdowna şablonên Maneyê jî du heb hene, yek di nav dropdownê de ku naxebitin ya din piştî hilbijartinê xuya dibin ku ew jî ne hewce ne, tenê lazim e di nav qutiyê de be. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:30, 16 tebax 2026 (UTC)
:::::Di "Tercîhên min" de butona "Hemû cache paqij bike" lazim e cache-yên etîket, dever û kategoriyê jî paqij bike. Niha nake. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:31, 16 tebax 2026 (UTC)
::::::Pill-ên Hevmane/Dijmane etc. lazim in di derveyî qutiya kelîmeyê de xuya bibin piştî nivîsandinê. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:33, 16 tebax 2026 (UTC)
:::::::"- beşek hilbijêre -" bike "- binebeşên din hilbijêre -" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:34, 16 tebax 2026 (UTC)
::::::::Çavkanî section dropdown linebreak dixe berîya "* şablon" lazim e linebreak piştî navê şablonê were. Îkonên lînk û înfo li vir jî naxebitin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:35, 16 tebax 2026 (UTC)
:::::::::"Binêre herwiha (bnr-h)" bike "Herwiha binêre (h-bnr)"
:::::::::"Şablona bnr-h li qismê herî jor a rûpelê" bike "Şablona h-bnr li qismê herî jor a rûpelê" [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:36, 16 tebax 2026 (UTC)
::::::::::@[[Bikarhêner:Ghybu|Ghybu]] Bi min problem nema. Fikrê te? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:18, 17 tebax 2026 (UTC)
:::::::::::Temam. Çend tişt 1) Forma dema bê bin forma dema niha. 2) Beşa Tewandinê (ji bo lêkeran) vitira diminî an ji li gor danayên lêkerê (x-gh|reh, ...) tên tijî kirin. 3) Di qutiyên forma dema niha, borî, ... de zû nayê nivîsandin: înterneta te pirr rind e, tu ferq nakî? :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 17:46, 17 tebax 2026 (UTC)
::::::::::::Te wisa got înterneta min qut bû ew çi îş e :D Înterneta min jî pir hêdî ye feqat min ferq nekiribû. Eynî problem heye. Ji bo lêkeran tewandin bila niha wisa bimîne em dûre rojane bikin, jixwe piştî rastkirina şablonên jêgirtinan ez ê dîsa vegerim vê skrîptê. Niha lazim e bala xwe bidim jêgirtinan. Herwiha em êdî qerarek bidin tewandina lêkeran diçin beşa Tewandinê an beşa rêzimanê? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:03, 17 tebax 2026 (UTC)
:::::::::::::Eger tenê yên x-gh/ngh dikî em dikarin tewandina wê niha zêde bikin, feqat p û h zor in ne wisa? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:25, 17 tebax 2026 (UTC)
:Bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/Gadget-test.js&curid=1358395&diff=6334375&oldid=6334373] min ferqek his nekir, piştî nivîsînê (lose focus?) bila DOM were nûkirin ne baştir e? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:28, 17 tebax 2026 (UTC)
== [[Mediawiki:Gadget-QQ.js]] ==
Ev amûr di şikle Minerva de. Her daîm vê xeletiyê dide:
<syntaxhighlight lang="js">
TypeError: Cannot read properties of null (reading 'addEventListener') at css () at runScript () at Array.<anonymous> () at flushCssBuffer ()
</syntaxhighlight> [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:29, 18 tîrmeh 2026 (UTC)
:* [[Bikarhêner:Wikihez/skrîpt/js/Gadget-QQ.js]] --> [[Mediawiki:Gadget-QQ.js]]
:* [[Bikarhêner:Wikihez/Gadget-QQ.css]] --> [[Mediawiki:Gadget-QQ.css]]
:Ez dibêjim çêbû lê ka çend roj biceribînim. Tiştên ku hatin sererastkirin:
:# Google Books API Key dikarî zêde bikî, dixe cookies û di gerokê de dimîne. Berê bê API Key dixebitiya lê êdî mecbûr e ku API Key-a te hebe, bidestxistina wê ne zêde zor e lê dîsa jî tevlihev e.
:# Êdî di mobîlê de dixebite, heta di mobîlê de xweştir xuya dike.
:# Moda tarî
:# OOUI dewsa jquery.ui
:# Li qutiyê çi dixwazî zêde bikî, dikare li gorî îsm, sifet, fiîl bitewîne. Lê ji bo fiîl tenê dikare çend heb inflection bîne.
:# Butona paqij bike lê zêde kir.
:# Çend paqijiyên jêgirtinên Google Kitêbê niha hene.
:[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 23:38, 18 tîrmeh 2026 (UTC)
::Tewandina fiîlan û xeletiya modulê lazim e were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:40, 21 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-wikt.add-examples.js]] ==
Min ev amûr hinek rojane kir feqat çend problem hene berî ku bar bikin.
Tiştên ku min guhart:
# Êdî li sayfeyên bi {{ş|maneid}} jî dixebite,
# Cihê di wîkîtekstê de yê ku jêgirtin were îlawekirin baştir bû,
# [[Mediawiki:Gadget-citoid-helper.js|citoid helper]] zêde kir ji bo ku bi hêsanî çavkanî bi riya doi, isbn an urlyê werin tijîkirin.
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.css]] --> [[MediaWiki:Gadget-wikt.add-examples.css]]
* [[Bikarhêner:Wikihez/Gadget-wikt.add-examples.js]] --> [[MediaWiki:Gadget-wikt.add-examples.js]]
- --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 00:50, 19 tîrmeh 2026 (UTC)
:Min "wikt." qismên cache kirinê ya wîkiya fransî bi [[MediaWiki:Gadget-StorageUtils.js]] guhartiye. loma ji bîr nekin wê amûrê li [[MediaWiki:Gadgets-definition]] zêde bikin. û navê amûrê jî biguhêrînin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:15, 23 tîrmeh 2026 (UTC)
::Berê li ser WF:ceribandin dixebitî. niha naxebite. Divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 22:10, 7 tebax 2026 (UTC)
== Gadget-translation editor ==
* [[MediaWiki:Gadget-translation editor.js]]: divê formata en.wîktê were bikaranîn ji ber ku me gelek tercime ji en.wîktê kopî kir û ev js hîn nikare formata wekî li jêr bi kar bîne.
<syntaxhighlight lang="wikitext">
* {{Z|sh}}:
*: Kirîlî:
*: Latînî
</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== [[Mediawiki:Gadget-linkify.js]] ==
* ''rûpeleke'' wekî<syntaxhighlight lang="wikitext">[[rûpeleke]]</syntaxhighlight> lînk dike, bi regexê belkî dikare wisa bike <syntaxhighlight lang="wikitext">[[rûpel]]eke</syntaxhighlight>
—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
* Amûreke din wekî [[User:Wikihez/Gadget-linkify.js|linkify]] lazim e ku herfên mezin biçûk bike ji bo kurmancî.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:{{r|Bikarhêner}} Merheba bira, dikarî viya jî bar bikî?:
* [[Special:PermanentLink/6291974|linkify]] --> [[Mediawiki:Gadget-linkify.js]]
Spas--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:42, 29 tîrmeh 2026 (UTC)
:Dûre [https://ku.wiktionary.org/wiki/MediaWiki:Gadget-start.js#L-283 li vir] wiktviLinkify bike kuLinkify xêra xwe [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:51, 29 tîrmeh 2026 (UTC)
::Min ji bîr kir lê zêde bikim niha nikare "Dİ" bike "di", xeletiyeke berbelav a di nivîsên kurdî de, herfên din ên wekî ll yan rr jî hewce ne werin zêdekirin. Dûre em zêde bikin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 21:42, 29 tîrmeh 2026 (UTC)
:::Min nû ferq kir, sayfeyên ku hîn nehatine çêkirin wîkîlînk nake. Problemeke mezin e divê were rastkirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:15, 30 tîrmeh 2026 (UTC)
::::Na, lînk dike, tiştekî din heye... [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:49, 31 tîrmeh 2026 (UTC)
== Amûra manayê ==
Amûreke din ji bo nivîsina maneyê wekî Nimûne zêde bike lazim e [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6276876 js] [https://ku.wiktionary.org/w/index.php?title=%C5%9Eablon:ceribandin2&oldid=6265629 şablon] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6265642 css] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:12, 23 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-jeder.js]] ==
@[[Bikarhêner:Ghybu|Ghybu]] Çavkaniyên jêderê di moda pêşdîtinê de jêderê vedişêre, ew jî min gelek eciz dike wextê çavkanî zêde dikim. Min ji Claude pirsî û kodan nîşanî wê da, wisa cewab da:
Yes — and this file actually explains the whole thing precisely, better than my earlier guess about extract-stripping.
What's happening: the CSS rule
<syntaxhighlight lang="css">
.jeder{
display:none;
}
</syntaxhighlight>
hides every citation by default, *unconditionally*. The only thing that ever makes it visible again is the gadget's JS: it finds each `.jeder`, builds the short "`— (year, author, +)`" summary from the `.j-pewist` fields, and either reveals the short version or — for citations with no `.j-pewist` fields — explicitly sets `display:inline` on the original.
So visibility of citations depends entirely on this JS running. In the normal article view (Image 1) it runs, so you see the collapsed "`(1918, E. Rehmî Ji Mala Hekariyan, Amadekar: M. E. Bozarslan, +)`" form. In whatever generated Image 2 — a hover-preview/extract/API render — gadget JS doesn't execute, so nothing ever removes the `display:none`, and the citation vanishes completely rather than degrading to "still visible, just not collapsed."
<b>The fix:</b> flip the default so the *un-collapsed* state is the safe fallback, and let JS do the hiding only after it successfully builds the short version. That way, any context without JS shows the full citation instead of nothing.
CSS — remove the blanket hide:
<syntaxhighlight lang="css">
.jeder {
/* intentionally no display:none here — see Gadget-... .js */
}
</syntaxhighlight>
JS — do the hiding from script, after the short form exists:
{{veşêre-ser}}
<syntaxhighlight lang="js">
// Ji bo şablonên [[Şablon:jêder]] hatiye nivîsandin.
// Parametreyên ku di navbera "<span class="j-pewist">...</span>" an "{{j-pêwîst|...}}" de ne venaşêre.
$(function() {
function nivisaKurtTevliBike(sund, nivisKurt) {
var zede = $('<a>+</a>')
.addClass('click')
.attr('href', '#')
.attr('title', 'zêdetir')
.css('font-weight', 'bold');
var kurt = $(' <a>↩</a>')
.addClass('click-kurt')
.attr('href', '#')
.attr('title', 'veşêre')
.css('font-weight', 'bold');
$('<span>' + nivisKurt + '</span>')
.append(zede).append(')')
.addClass('jeder-kurt')
.css('font-size', '0.85em')
.insertAfter(sund);
sund.append(' ').append(kurt);
// Only hide the full citation once the short summary actually exists.
// If this script never runs (no-JS clients, hover-preview extracts,
// action=render, etc.), the full citation stays visible instead of
// silently disappearing.
sund.css('display', 'none');
}
function vesere() {
$('.jeder').each(function() {
var elmTab = $(this).find('.j-pewist').clone();
if (elmTab.length) {
var elm = '';
for (var i = elmTab.length - 1; i >= 0; i--) {
elm = elmTab.eq(i).html() + ', ' + elm;
}
var nivisKurt = ' — (' + elm;
nivisaKurtTevliBike($(this), nivisKurt);
}
// else: no j-pewist fields to shorten — leave the full citation
// as it already renders (visible), nothing to do.
});
}
vesere();
$(".click").click(function(e) {
e.preventDefault();
$(this).parent('.jeder-kurt').css('display', 'none');
$(this).parent('.jeder-kurt').prev().css('display', 'inline');
});
$(".click-kurt").click(function(e) {
e.preventDefault();
$(this).parent('.jeder').css('display', 'none');
$(this).parent('.jeder').next().css('display', 'inline');
});
});
</syntaxhighlight>
Net effect:
- JS-enabled readers (normal article view): identical experience to today — collapsed short form with `+`/`↩` toggle.
- No-JS contexts (previews, extracts, screen readers before JS loads, print, etc.): full citation is shown instead of nothing — a real regression fix rather than a cosmetic one.
One caveat worth being upfront about: this fixes it *if* the preview context renders the gadget's HTML/CSS but doesn't execute gadget JS (which is the far more common case, and what this code strongly suggests is happening). If Image 2 instead comes from a pipeline that strips the `.jeder`/`.jegirtin-text` elements out of the HTML entirely before this CSS/JS even gets a chance to apply, no on-wiki CSS/JS change can help — that'd need the extract-generation config changed instead. Given what this gadget file shows, though, I'd bet on the CSS-hide explanation.
{{veşêre-bin}}
Em dûre biceribînin eger xeletî tinebe, vî tiştî jî rast bikin. --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:00, 23 tîrmeh 2026 (UTC)
:Ez APIya MediaWiki rind nizanim belkî Claude dizane:) Em nikarin bibêjin dema ku moda "''action=edit''" e fonksiyona <code>function vesere()</code> bi kar meyîne. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:57, 24 tîrmeh 2026 (UTC)
::Na, di moda "edît"-ê de <code>var elmTab = null</code>... [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:13, 24 tîrmeh 2026 (UTC)
?:
<syntaxhighlight lang="js">
if (
mw.config.get('wgAction') === 'edit' ||
mw.config.get('wgAction') === 'submit'
){
var elmTab = [];
} else {
var elmTab = $(this).find('.j-pewist').clone();
}
</syntaxhighlight>
--[[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:31, 24 tîrmeh 2026 (UTC)
:Ez jî nizanim Claude çiqas ji mediawiki fêm dike lê heta niha ti tişt nîne ku min jê pirs kir û nikaribû çêke, bila wekî reklam nebe feqat Claude mikemmel e, dibêjim "li vir çima xeletiyeke wisa derdikeve" di dosyeya 2000 rêzî de diçe tiştê xelet tenê rast dike tiştekî din naguherîne. Chatgpt û aistudio qet wisa nakin, eynî dosyeyê deh cara dinivîsin, deh netîceyên cuda derdikevin :D
:Em dikarin vê amûrê wextê edit û submitê neaktîv bikin wekî <syntaxhighlight lang="js">if (!(action === "edit" || action === "submit")) {
:return;</syntaxhighlight> lê wê demê display:none wê çibe nizanim. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:23, 24 tîrmeh 2026 (UTC)
::display:none ne hewce ye ma jixwe di nav kodê [https://ku.wiktionary.org/w/index.php?title=MediaWiki:Gadget-jeder.js&oldid=6069025#L-49 L-49] de inline/none heye? [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:53, 24 tîrmeh 2026 (UTC)
:Min bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/vector-2022.js&diff=prev&oldid=6290816#L-28] ceriband feqat nexebitî. "display:none" di pêşdîtinê de dixebite feqat js naşixule. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 14:43, 25 tîrmeh 2026 (UTC)
::Kodên JSê pir hez nakim (pêşta hukurina APIya MWyê hewce bû) dîsa jî ez ê seh bikim :) [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 16:49, 27 tîrmeh 2026 (UTC)
:::Ez niha bi vî şiklî [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/test.js&oldid=6290820] [https://ku.wiktionary.org/w/index.php?title=Bikarh%C3%AAner:Wikihez/common.css&oldid=6290821#L-8] bi kar tînim ti problem nîne. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:09, 27 tîrmeh 2026 (UTC)
== [[MediaWiki:Gadget-PagePreviews.js]] ==
Ez dibêjim em vê amûrê jî çêkin [[:en:MediaWiki:Gadget-PagePreviews.js]] --[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 10:56, 3 tebax 2026 (UTC)
= Moda tarî =
== [[Şablon:wîkîpediya]] ==
Rengê tarî de xirab e.--[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 19:36, 4 tebax 2026 (UTC)
= Bot =
Karên ku bi botê werin çêkirin ji kerema xwe li vir binivîsin.
== Kategorî:Biwêj ==
[[:Kategorî:Biwêj]] divê were paqijkirin, lîsteya madeyên {{ş|biwêj}} çêke, yên ku tê de bûn, kirin, man hene filtre bike yên din bi destî tê çêkirin.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Jêgirtin ==
Xistina jêgirtinên beşa [https://ku.wiktionary.org/w/index.php?search=insource%3A%2F%3D+*Ji+%5BWw%5D%C3%AAjey%C3%AA+*%3D%2F&title=Taybet%3AL%C3%AAger%C3%AEn&profile=advanced&fulltext=1&ns0=1&searchToken=8rh0bmw8dccsi7aynl6zli8uf ji wêjeyê] li beşa maneyê. —[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== Bota Toolforgeê ==
Botekî Toolforgeê lazim e ji bo ku xeletiyên biçûk wekî şablonên bi navên îngilîzî werin kurdîkirin yan jî valahiya beşan rast bike û hwd.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
:Min [[Bikarhêner:Wikihez/skrîpt/py/kuwiktkozmetik.py|paqijiyen kozmetîk]] hazir kir feqat hîn navên şablonan naguhêre. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 20:05, 4 tebax 2026 (UTC)
== Updatew ==
Bota [[Bikarhêner:Wikihez/skrîpt/py/updateW.py|updateW]] divê were rojanekirin. Yan jî yekî nû were çêkirin. Şablonên {{ş|W}} di beşa tercimeyan de sererast dike û zimanan rêz dike. Ya niha xeletiya navê zimanî dide.—[[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 16:17, 16 tîrmeh 2026 (UTC)
== sihir --> sêhr ==
Tercimeyên ''[[sêhr]]'' hemû dibêje ''sihir'' loma bi botê lazim e kirin ''sêhr''. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 17:43, 18 tîrmeh 2026 (UTC)
:Sihirbaz --> sêhrbaz [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:07, 18 tîrmeh 2026 (UTC)
== Tercimeyên dîroknas ==
Li tercimeyên [[dîroknas]], hevmaneyên wê hewce ye were îlawekirin. [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 18:44, 18 tîrmeh 2026 (UTC)
== Mewlûd ==
Sererastkirina jêgirtinên mewlûdê [https://ku.wiktionary.org/w/index.php?search=insource%3A%2Fw%5C%3AMela+His%C3%AAn%C3%AA+Batey%C3%AE%2F&title=Taybet%3AL%C3%AAger%C3%AEn&ns0=1&searchToken=8m0tguy3sqt7edr4sg0u8hedo] wekî [https://ku.wiktionary.org/w/index.php?title=dax%C3%AEdar&curid=284303&diff=6265851&oldid=6142285] [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 09:43, 20 tîrmeh 2026 (UTC)
== [[Şablon:jêder]] ==
Şablon jêder axlebî bi şiklê MM/YYYY sal tê de heye, bi botê em dikarin wana bixin {{para|sal}} [[Bikarhêner:Wikihez|Wikihez]] ([[Gotûbêja bikarhêner:Wikihez|gotûbêj]]) 11:07, 17 tebax 2026 (UTC)
bnfu26dzo58sh869klmdecxptzrgxfw
Şablon:J:Shamdinani Stories I:2026
10
1357905
6334418
6329776
2026-08-17T22:58:42Z
Wikihez
7344
6334418
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|nivîskar=Mela Seîd Şemdînanî|amadekar={{w|David N. MacKenzie}}; {{w|Ergin Öpengin}}|sernav=Shamdinani Stories I|weşanxane=Brill|cih=Leiden|sal=~1915|serî=Brill Kurdish Studies|cild=3|sala weşandinê=2026|çap=|rûpel={{{rûpel|{{{r|}}}}}}|isbn=978-90-04-75443-0|url=https://brill.com/edcollbook-oa/title/74006?language=en}}<noinclude>{{jgcat|ku}}<templatedata>
{
"params": {
"rûpel": {
"aliases": [
"r"
]
}
},
"description": "Mela Seîd Şemdînanî, Amadekar: David N. MacKenzie; Ergin Öpengin Shamdinani Stories I"
}
</templatedata></noinclude>
swkd7xfcd1c6048arhtogjjud4s4pjg
6334495
6334418
2026-08-18T00:17:46Z
Wikihez
7344
6334495
wikitext
text/x-wiki
{{{{#if:{{{jêgirtin|{{{jg|}}}}}}|jêder-}}kitêb|ku<!--
-->|jêgirtin={{{jêgirtin|{{{jg|}}}}}}<!--
-->|rûpel={{{rûpel|{{{r|}}}}}}<!--
-->|nivîskar=Mela Seîd Şemdînanî|amadekar={{w|David N. MacKenzie}}; {{w|Ergin Öpengin}}|sernav=Shamdinani Stories I|weşanxane=Brill|cih=Leiden|sal=~1915|serî=Brill Kurdish Studies|cild=3|sala weşandinê=2026|çap=|isbn=978-90-04-75443-0|url=https://brill.com/edcollbook-oa/title/74006?language=en}}<noinclude>{{jgcat|ku}}<templatedata>
{
"params": {
"rûpel": {
"aliases": [
"r"
]
}
},
"description": "Mela Seîd Şemdînanî, Amadekar: David N. MacKenzie; Ergin Öpengin Shamdinani Stories I"
}
</templatedata></noinclude>
j9ccg73z0zv5szqoz1i04j3uvsn7sih
bazeber
0
1357907
6334417
6298333
2026-08-17T22:58:06Z
Wikihez
7344
/* Navdêr */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334417
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Bilêvkirin ===
* {{ku-kîte}}
=== Navdêr ===
[[Wêne:Stepping Stones, Egton - geograph.org.uk - 570400.jpg|thumb|bazeber]]
{{navdêr|ku|c=m}}
# {{e|ku|Şemdînan}} [[kevir|Kevirê]] ku li [[nav]]a [[av]]ê an [[çem]]ê [[tê danîn]] [[da ku]] [[meriv]] [[ling]]ê [[xwe]] [[dan|bide]] [[ser]] û bi [[gav]]an [[di ser re]] [[derbas bûn|derbasî]] [[aliyê din]] [[bûn|bibe]].
#: {{alt-r|ku|bazber}}
#* {{J:ku:Shamdinani Stories I:2026|jêgirtin=Dibêjin di nihalekê da rîwîyek hebû. Awek nîw boş bi nîweka nihalê da dihate xar. '''Bazeberek''' hebû, rîwî lê derbaz dibû her wextê ḥez kiriba.}}
#* {{jêder-kitêb|ku|jêgirtin=Kevir di nav avê de datanîn û mirovan di ser re gav dida û diçûn qama dî. Gûzereşiyan ji vê rêgeha '''bazeberê''' re digot gav.|sal=2022|nivîskar=Omer Dilsoz|url=http://books.google.com.tr/books?id=JBuKEAAAQBAJ&pg=PA26&dq=%22wan+bazeberan%22+OR+%22bazeber%C3%AA%22+OR+%22bazeberin%22+OR+%22bazeberek%C3%AA%22+OR+%22bazebera%22+OR+%22w%C3%AA+bazeber%C3%AA%22+OR+%22bazeberino%22+OR+%22bazeberine%22&hl=&cd=3&source=gbs_api|sernav=Gûzereş|weşanxane=Omer Dilsoz|ISBN=9786257568722|rûpel=26}}
# {{e|ku|mecazî}} [[kar|Karê]] [[giran]].
#* {{jêder-kitêb|ku|jêgirtin=Bivê-nevê ye li ser her kesê dixwazit xwe li '''bazeberên''' giran bavêjit, hûr û dûr binêrit heyne wê ser û ser li tehtekê bikevit.|nivîskar=Mele Mehmûd Dêrşewî|sernav=Miştaxa Çiya Ji Gotinên Pêşîya|weşanxane=Rasheed M. AB|cih=Stockholm|sal=1989|isbn=978-91-7970-535-0}}
==== Tewandin ====
{{ku-tewîn-nav/nû|mê}}
==== Bi alfabeyên din ====
* {{ku-ar|بازەبەر}}
==== Etîmolojî ====
{{ji?|ku}}
==== Werger ====
{{werger-ser|kevirê ku li nava av an çemê tê danîn da ku meriv lingê xwe bide ser û bi gavan di ser re derbasî aliyê din bibe|1}}
* {{Z|en}}: {{W|en|stepping stone}}
* {{Z|tr}}: {{W|tr|atlama taşı}}
{{werger-bin}}
{{werger-ser|karê giran|2}}
{{werger-bin}}
=== Çavkanî ===
* {{kovara kurmancî|hejmar=63|sernav=Peyvên devera Şemdînan|nivîskar=Ergîn Opengîn|r=3}}
* {{Chyet|r=27}}
* {{Zana Farqînî|r=101}}
* {{Ç:ku:Henbane borîne|بازەبەر}}
ol878fj2abj2ld3zt69cdslwly82y9k
Bikarhêner:Wikihez/Gadget-test.css
2
1358394
6334322
6334302
2026-08-17T13:51:22Z
Wikihez
7344
6334322
css
text/css
/* ===================================================================
#editform while the wizard is active
=================================================================== */
/* Keeps #editform genuinely laid out instead of display:none*/
.wiktku-offscreen {
position: absolute !important;
left: -9999px !important;
top: 0 !important;
width: 100%;
max-width: 100%;
visibility: hidden;
}
/* ===================================================================
Base scale
=================================================================== */
#wiktku-start-form {
font-size: 0.92em;
line-height: 1.35;
}
#wiktku-start-form * {
box-sizing: border-box;
}
.wiktku-addlang-notice {
margin: 0 0 0.8em;
padding: 0.6em 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 4px;
}
/* ===================================================================
Step cards
a compact card with a left accent bar, so each step reads as
a distinct unit
=================================================================== */
.wiktku-start-step {
padding: 0.7em 0.9em;
margin-bottom: 0.6em;
border: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
border-left: 4px solid var(--wikt-palette-blue-9, #3366cc);
border-radius: 6px;
background: var(--wikt-palette-white);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
transition: border-left-color 0.15s ease, box-shadow 0.15s ease;
}
.wiktku-start-step:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.wiktku-start-step:focus-within {
border-left-color: var(--wikt-palette-blue-9, #3366cc);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
/* Fuses the posparams card onto the bottom of the pos card (shared left
accent bar, no border/gap) since posparams always renders directly
after #wiktku-start-pos once a pos is chosen. */
#wiktku-start-pos {
border-radius: 6px 6px 0 0;
margin-bottom: 0;
border-bottom: none;
}
#wiktku-start-posparams {
border-radius: 0 0 6px 6px;
border-top: none;
padding-top: 0.3em;
}
#wiktku-start-posparams .wiktku-start-step-name {
display: none;
}
/* Scoped and !important: this is a real <h3> tag (see start.js), so the
skin's default heading styles would otherwise override a plain class
rule. Coloured to match the card's accent bar; .wiktku-start-step-question
below stays lighter/muted so the two don't compete. */
#wiktku-start-form .wiktku-start-step-name {
font-size: 1.1em !important;
font-weight: 700;
letter-spacing: 0.03em;
margin: 0 0 0.3em;
line-height: 1.2;
color: var(--wikt-palette-blue-9, #3366cc);
border: 0;
padding: 0;
}
/* Sized close to the heading so the card reads consistently instead of
the text below it looking like fine print. */
.wiktku-start-step-question {
font-size: 1em;
font-weight: 400;
color: var(--wikt-palette-grey-9, #555);
margin: 0 0 0.5em;
line-height: 1.45;
}
.wiktku-start-step label {
padding-right: 1.2em;
font-size: 1em;
}
/* Muted hint text next to a field, styled to read as a hint rather than
another question competing with .wiktku-start-step-question. */
.wiktku-step-hint {
font-size: 0.88em;
font-style: italic;
color: var(--wikt-palette-grey-8, #72777d);
margin: 0 0 0.5em;
line-height: 1.4;
}
/* Same specificity as the rule just above, so it must come after to
actually win: the name label isn't a field label needing right
padding or the form's base label size, it's a heading. */
#wiktku-start-form .wiktku-start-step-name label {
padding-right: 0;
font-size: inherit !important;
color: inherit;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
padding: 5px 8px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 4px;
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
input[type="text"].wiktku-start-step-field:focus {
border-color: var(--wikt-palette-blue-9, #3366cc);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9, #3366cc);
outline: 0;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
/* Only used by selects that opt out via .wiktku-plain-select; other
selects get their width from the searchable picker instead (see
input.wiktku-ac-input). */
select.wiktku-start-narrow,
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
#wiktku-start-spinner {
float: right;
margin-left: 1em;
}
#wiktku-start-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5em;
padding: 0.6em 0.2em;
position: sticky;
bottom: 0;
background: var(--wikt-palette-white);
border-top: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
z-index: 5;
}
/* Quiet, non-competing text — sits next to the cache buttons so the
person can tell at a glance whether their latest edits are backed
up, without it fighting the buttons for attention. */
#wiktku-last-saved {
font-size: 0.85em;
color: var(--wikt-palette-grey-9, #767676);
white-space: nowrap;
}
/* ── Tercîh (settings) icon button — sits in the footer next to the
cache buttons. Uses the Codex "settings" (color-subtle) icon from
Commons directly rather than an OOUI icon-pack module, so it renders
the exact requested glyph without pulling in an extra dependency. */
.wiktku-prefs-btn {
width: 2.2em;
height: 2.2em;
flex: 0 0 auto;
padding: 0;
border: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
border-radius: 2px;
background-color: var(--wikt-palette-white);
background-image: url(//upload.wikimedia.org/wikipedia/commons/e/e0/Codex_icon_settings_color-subtle.svg);
background-repeat: no-repeat;
background-position: center;
background-size: 1.2em 1.2em;
cursor: pointer;
}
.wiktku-prefs-btn:hover {
background-color: var(--wikt-palette-grey-0, #f8f9fa);
}
.wiktku-prefs-btn:active {
background-color: var(--wikt-palette-grey-1, #eaecf0);
}
/* Tercîh dialog body — OOUI's bare ProcessDialog body has no inset of
its own, so text/fields otherwise sit flush against the window edges. */
.wiktku-prefs-dialog-body {
padding: 1em 1.2em;
}
/* ── "Bişîne" click feedback ── */
@keyframes wiktku-btn-flash-pulse {
0% { box-shadow: 0 0 0 0 rgba(51, 102, 204, 0.55); }
70% { box-shadow: 0 0 0 9px rgba(51, 102, 204, 0); }
100% { box-shadow: 0 0 0 0 rgba(51, 102, 204, 0); }
}
#wiktku-unhide-editform.wiktku-btn-flash {
animation: wiktku-btn-flash-pulse 0.5s ease-out;
}
/* Persistent state while the wikitext is being built/submitted, set
alongside OOUI's own setDisabled(true) so it reads clearly even if
the skin's default disabled style is subtle. */
#wiktku-unhide-editform.wiktku-btn-sending .oo-ui-buttonElement-button {
background-color: var(--wikt-palette-grey-9, #767676) !important;
border-color: var(--wikt-palette-grey-9, #767676) !important;
cursor: progress;
opacity: 1 !important;
}
/* Looks like OOUI's disabled state but isn't one — the button stays
clickable so its handler can still run and explain why (see the
wiktkuNotify call next to wiktkuContentGateOk in start.js). */
#wiktku-unhide-editform.wiktku-btn-soft-disabled .oo-ui-buttonElement-button {
opacity: 0.5;
cursor: not-allowed;
}
/* ── "Beşên din" picker ── */
.wiktku-besendin-picker {
margin: 0.6em 0 1em;
}
.wiktku-besendin-picker .wiktku-besendin-select {
width: 100%;
max-width: 24em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield,
#wiktku-start-form textarea.wiktku-word-input {
resize: vertical !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px;
transition: border-color 0.1s ease, box-shadow 0.1s ease;
}
/* Modern focus state to match the searchable-picker inputs. */
#wiktku-start-form textarea.wiktku-start-step-field:focus,
#wiktku-start-form textarea.wiktku-start-narrow:focus,
#wiktku-start-form textarea.wiktku-dfn-subfield:focus,
#wiktku-start-form textarea.wiktku-word-input:focus {
border-color: var(--wikt-palette-blue-9, #3366cc);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9, #3366cc);
outline: 0;
}
/* Standard: every textarea in the form is full width by default */
#wiktku-start-form textarea:not(.wiktku-start-narrow) {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
max-width: 100%;
min-width: 8em;
height: calc(1.7em + 10px);
min-height: calc(1.7em + 10px);
padding-top: 4px !important;
padding-bottom: 4px !important;
line-height: 1.35 !important;
resize: none !important;
}
/* Exception: "Sernav" / "Sernavê orjînal" citation-source fields (kitêb,
kovar, malper, …) — long enough to be worth 2 rows and manual resizing. */
#wiktku-start-form textarea.wiktku-dfn-quote-src-input.wiktku-dfn-quote-src-input--title {
height: calc(2 * 1.35em + 10px);
min-height: calc(2 * 1.35em + 10px);
}
/* ── Manual drag-to-resize handle ── */
#wiktku-start-form textarea:not(.wiktku-start-narrow) {
resize: none !important;
}
/* Inline modifier button (⊕) */
/* Each word is always a plain box (never collapses into a pill shape); its
attached-modifier chips, if any, sit on their own line right above the
box, and the box + its ⊕/× buttons sit on their own line below that. */
.wiktku-word-li {
display: block;
}
/* ── ⊕-modifier word lists (Hevmane/Dijmane/Formên alternatîv/Termên
koordîne/Jornav/Jêrnav — both the top-level steps and the per-sense
sub-groups): every word is its own always-visible, always-editable box,
one per line — no packing/wrapping of multiple words onto one row. ── */
#wiktku-start-form .wiktku-dfn-sublist > li.wiktku-word-li {
margin-bottom: 6px;
}
/* Modifier chip row: sits above the word's box (DOM order in start.js
already places it first; this just spaces it out from the box below). */
#wiktku-start-form .wiktku-word-mods {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 3px;
}
#wiktku-start-form .wiktku-word-mod-chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--wikt-card);
color: var(--wikt-ink-2);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
font-size: 11.5px;
padding: 2px 4px 2px 8px;
cursor: pointer;
}
#wiktku-start-form .wiktku-word-mod-chip-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
#wiktku-start-form .wiktku-word-mod-chip-remove:hover {
color: var(--wikt-danger);
}
/* Word box + its ⊕ (add modifier) / × (remove word) buttons, side by side. */
#wiktku-start-form .wiktku-word-box-row {
display: flex;
align-items: center;
gap: 4px;
}
#wiktku-start-form .wiktku-word-box-row .wiktku-word-input {
flex: 1 1 auto;
}
#wiktku-start-form .wiktku-word-remove {
flex: 0 0 auto;
width: 1.6em;
height: 1.6em;
padding: 0;
line-height: 1;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
color: var(--wikt-muted);
font-size: 1em;
cursor: pointer;
}
#wiktku-start-form .wiktku-word-remove:hover {
border-color: var(--wikt-danger);
color: var(--wikt-danger);
}
.wiktku-mod-btn {
flex: 0 0 auto;
width: 1.6em;
height: 1.6em;
padding: 0;
line-height: 1;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
color: var(--wikt-palette-blue-9, #3366cc);
font-size: 1em;
cursor: pointer;
}
.wiktku-mod-btn:hover {
border-color: var(--wikt-palette-blue-9, #3366cc);
background: var(--wikt-palette-paleblue, #eaf3ff);
}
.wiktku-mod-picker {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
width: 100%;
margin-top: 0.15em;
padding: 0.35em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px;
background: var(--wikt-palette-white);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.wiktku-mod-pill {
padding: 2px 8px;
border: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
border-radius: 12px;
background: var(--wikt-palette-paleblue, #eaf3ff);
font-size: 0.85em;
white-space: nowrap;
cursor: pointer;
}
.wiktku-mod-pill:hover {
border-color: var(--wikt-palette-blue-9, #3366cc);
background: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-white);
}
.wiktku-mod-pill:disabled,
.wiktku-mod-pill.wiktku-mod-pill-used {
border-color: var(--wikt-palette-grey-3, #e0e0e0);
background: var(--wikt-palette-lightgrey);
color: var(--wikt-palette-grey-3, #999);
cursor: not-allowed;
opacity: 0.6;
}
.wiktku-mod-pill:disabled:hover,
.wiktku-mod-pill.wiktku-mod-pill-used:hover {
border-color: var(--wikt-palette-grey-3, #e0e0e0);
background: var(--wikt-palette-lightgrey);
color: var(--wikt-palette-grey-3, #999);
}
.wiktku-mod-val-wrap {
display: flex;
align-items: center;
gap: 0.25em;
width: 100%;
margin-top: 0.15em;
}
.wiktku-mod-val-input {
flex: 0 1 14em;
max-width: 14em;
padding: 3px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
}
.wiktku-mod-confirm,
.wiktku-mod-cancel {
flex: 0 0 auto;
width: 1.6em;
height: 1.6em;
padding: 0;
line-height: 1;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-mod-confirm {
color: #178a17;
border-color: #178a17;
}
.wiktku-mod-cancel {
color: var(--wikt-palette-red-9, #d33);
border-color: var(--wikt-palette-red-9, #d33);
}
.wiktku-resize-wrap {
position: relative;
display: block;
}
/* Separate from .wiktku-resize-wrap (used only for textareas flagged with
a linebreak); plain position:relative anchor for the backdrop, no size of its own. */
.wiktku-linebreak-wrap {
position: relative;
display: block;
}
.wiktku-resize-grip {
position: absolute;
right: 2px;
bottom: 2px;
width: 14px;
height: 14px;
cursor: ns-resize;
touch-action: none;
opacity: 0.5;
z-index: 2;
background-image: linear-gradient(135deg, transparent 0 50%, var(--wikt-palette-grey-9, #767676) 50% 100%);
background-repeat: no-repeat;
}
.wiktku-resize-grip:hover,
.wiktku-resize-grip:active {
opacity: 0.9;
}
/* ===================================================================
Searchable dropdown (wrapNativeSelect in start.js)
.wiktku-native-select-wrap > input.wiktku-ac-input (search box)
> ul.wiktku-ac-results (result list)
> select (hidden, holds the value)
=================================================================== */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
vertical-align: top;
}
/* Matches the width the native select would have had (see
select.wiktku-start-narrow). */
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
padding: 3px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
color: inherit;
font-size: inherit;
font-family: inherit;
transition: border-color 0.1s ease, box-shadow 0.1s ease;
}
input.wiktku-ac-input:focus {
border-color: var(--wikt-palette-blue-9);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9);
outline: 0;
}
input.wiktku-ac-input::placeholder {
color: var(--wikt-palette-grey-9);
opacity: 1;
}
/* Hide Safari/Chrome's built-in search decorations. */
input.wiktku-ac-input::-webkit-search-decoration,
input.wiktku-ac-input::-webkit-search-cancel-button,
input.wiktku-ac-input::-webkit-search-results-button {
-webkit-appearance: none;
appearance: none;
}
/* ── Result list ── */
/* Stays below MediaWiki's own .mw-notification-area (z-index: 10000) so the dropdown never covers a toast notification. */
ul.wiktku-ac-results {
position: absolute;
z-index: 500;
top: 100%;
bottom: auto;
left: 0;
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 13em;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
ul.wiktku-ac-results li {
margin: 0;
padding: 4px 8px;
cursor: pointer;
font-size: 0.92em;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* .wiktku-ac-active is the keyboard cursor; hover mirrors it; :active
covers the touch press state. */
ul.wiktku-ac-results li.wiktku-ac-active,
ul.wiktku-ac-results li:hover {
background: var(--wikt-palette-lightblue);
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* Touch devices: bigger tap targets, no sticky hover. */
@media (pointer: coarse) {
ul.wiktku-ac-results li {
padding: 6px 10px;
min-height: 2.2em;
font-size: 1em;
}
ul.wiktku-ac-results li:hover {
background: transparent;
}
ul.wiktku-ac-results li.wiktku-ac-active {
background: var(--wikt-palette-lightblue);
}
input.wiktku-ac-input {
/* >=16px stops iOS Safari zooming the page when the box is focused. */
font-size: 16px;
min-height: 2.2em;
}
}
/* Inline preview + confirm panel shown before inserting an auto-content
pronunciation template, part of the form layout rather than a browser
confirm()/prompt() dialog. */
.wiktku-pron-preview {
margin: 6px 0;
padding: 8px 10px;
border: 1px solid var(--wikt-palette-blue-9, #3366cc);
border-radius: 6px;
background: var(--wikt-palette-paleblue, #eaf3ff);
}
.wiktku-pron-preview-label {
font-size: 0.88em;
font-weight: 600;
color: var(--wikt-palette-grey-9, #555);
margin-bottom: 3px;
}
.wiktku-pron-preview-body {
font-family: monospace;
font-size: 1em;
white-space: pre-wrap;
word-break: break-word;
margin-bottom: 6px;
}
.wiktku-pron-preview-actions {
display: flex;
gap: 6px;
}
.wiktku-pron-preview-confirm,
.wiktku-pron-preview-cancel {
font-size: 0.88em;
padding: 3px 10px;
border-radius: 4px;
cursor: pointer;
border: 1px solid var(--wikt-palette-lightgrey, #a2a9b1);
background: var(--wikt-palette-white, #fff);
}
.wiktku-pron-preview-confirm {
border-color: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-blue-9, #3366cc);
font-weight: 600;
}
.wiktku-pron-preview-confirm:disabled {
opacity: 0.5;
cursor: wait;
}
.wiktku-pron-preview-cancel:hover {
background: var(--wikt-palette-grey-0, #f8f9fa);
}
/* Template pickers fill their container since the search box, not the
<select>, is what the user sees. */
.wiktku-pron-wrap .wiktku-native-select-wrap,
.wiktku-etym-wrap .wiktku-native-select-wrap,
.wiktku-pron-wrap .wiktku-native-select-wrap input.wiktku-ac-input,
.wiktku-etym-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
width: 100%;
}
.wiktku-pron-wrap .wiktku-native-select-wrap input.wiktku-ac-input,
.wiktku-etym-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
font-size: 0.88em;
}
.wiktku-cavkani-wrap .wiktku-native-select-wrap,
.wiktku-cavkani-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
width: 100%;
}
/* Template selector inside a sense block. */
.wiktku-dfn-tpl-row .wiktku-native-select-wrap input.wiktku-ac-input {
font-size: 0.88em;
}
/* Language/dialect rows: picker must line up with the word field beside it. */
.wiktku-lang-row .wiktku-native-select-wrap {
flex: 0 0 auto;
}
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form textarea.wiktku-dfn-nimune-text,
#wiktku-start-form .wiktku-dfn-jeder-group textarea.wiktku-dfn-nimune-quote,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t,
#wiktku-start-form textarea.wiktku-dfn-quote-src-manual,
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form .wiktku-tewandin-wrap textarea.wiktku-tewandin-text,
#wiktku-start-form textarea.wiktku-cavkani-text,
#wiktku-start-form textarea.wiktku-dfn-tercime,
#wiktku-start-form textarea.wiktku-dfn-izah {
--wiktku-ta-rows: 2;
height: calc(var(--wiktku-ta-rows) * 1.35em + 6px);
min-height: calc(var(--wiktku-ta-rows) * 1.35em + 6px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-dfn-izah {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
/* Mane, Nimûne + Jêgirtin (citation) boxes, and their tr/t sub-fields: at
least 4 rows so there's room to actually write a definition/example/quote.
Manual resize (see util.js) still lets the person grow or shrink any of
these afterward — this only sets where they start. */
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form textarea.wiktku-dfn-nimune-text,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
#wiktku-start-form .wiktku-dfn-jeder-group textarea.wiktku-dfn-nimune-quote,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t,
#wiktku-start-form textarea.wiktku-dfn-quote-src-manual {
--wiktku-ta-rows: 4;
}
/* Nudges the border when the box contains a typed/pasted linebreak, so
the user notices before it's silently turned into <br> on output; the
break itself is marked via a mirrored backdrop layer (see below). */
#wiktku-start-form textarea.wiktku-has-linebreak {
border-color: var(--wikt-palette-yellow6, #ac6600) !important;
}
/* Sits behind the textarea showing the same text, with a highlighted
marker at each line followed by a break; font/padding/line-height are
copied from the textarea via computed style, not shared CSS classes. */
.wiktku-linebreak-backdrop {
position: absolute;
inset: 0;
z-index: 0;
margin: 0;
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
color: transparent;
background: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
resize: none !important;
pointer-events: none;
}
/* Raise the real textarea above its backdrop so typing/selection/caret
behave completely normally; the backdrop's mark shows through only
where the real textarea's own background is transparent. */
#wiktku-start-form textarea.wiktku-has-linebreak-source {
position: relative;
z-index: 1;
background-color: transparent;
}
.wiktku-linebreak-mark {
background-color: var(--wikt-palette-yellow6, #ac6600);
color: var(--wikt-palette-white, #fff);
border-radius: 2px;
}
/* ── Tercime + Îzah boxes ── */
.wiktku-dfn-nonku-wrap {
display: flex;
flex-direction: column;
gap: 3px;
}
.wiktku-dfn-nimune-sub,
.wiktku-dfn-quote-sub {
margin-top: 3px;
margin-left: 1em;
padding-left: 0.6em;
border-left: 2px solid var(--wikt-palette-lightgrey);
display: flex;
flex-direction: column;
gap: 3px;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
min-width: 8em;
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.35 !important;
font-size: 0.88em;
}
/* Sense list (Mane step) — one sense visible at a time, switched via
.wiktku-dfn-sense-tabs. */
ol.wiktku-dfn-list {
list-style: none;
margin: 0;
padding: 0;
}
ol.wiktku-dfn-list > li.wiktku-dfn-sense {
margin-bottom: 0.9em;
}
/* Template selector row inside a sense block */
.wiktku-dfn-tpl-row {
margin-bottom: 3px;
}
/* ── Sub-field titles (Etîket, Kategorî, Hevmane, Dijmane, Nimûne,
Jêgirtin, …): each group reads as a small labelled field — the term
stands out, the hint stays quiet. ── */
.wiktku-dfn-subgroup {
margin-top: 0.5em;
padding-top: 0.4em;
border-top: 1px dashed var(--wikt-palette-grey-3, #e0e0e0);
}
.wiktku-dfn-subgroup:first-of-type {
margin-top: 0.3em;
padding-top: 0;
border-top: none;
}
.wiktku-dfn-sublabel {
display: block;
margin-bottom: 3px;
font-size: 0.88em;
}
.wiktku-dfn-sublabel-term {
font-weight: 600;
color: var(--wikt-palette-blue-9, #3366cc);
letter-spacing: 0.03em;
font-size: 0.92em;
}
.wiktku-dfn-sublabel-hint {
color: var(--wikt-palette-grey-9, #767676);
font-style: italic;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap,
.wiktku-tewandin-wrap,
.wiktku-cavkani-wrap {
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── POS section tabs — rounded pill style ── */
#wiktku-section-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 0.6em;
align-items: center;
}
#wiktku-section-tabs .wiktku-section-tab {
padding: 3px 12px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 999px;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.86em;
user-select: none;
transition: background 0.12s ease, border-color 0.12s ease;
}
#wiktku-section-tabs .wiktku-section-tab:hover {
border-color: var(--wikt-palette-blue-9, #3366cc);
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-blue-9, #3366cc);
border-color: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-white);
font-weight: 600;
}
/* ── Step-group tabs (Mane / Bilêvkirin / Beşên din — see STEP_GROUPS in
start.js) ── Sits directly under the pill-style section tabs above;
styled as an underlined secondary nav so the two bars stay visually
distinct. */
#wiktku-group-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 0.8em;
border-bottom: 2px solid var(--wikt-palette-lightgrey);
}
#wiktku-group-tabs .wiktku-group-tab {
position: relative;
padding: 7px 14px 8px;
margin-bottom: -2px;
border-bottom: 3px solid transparent;
border-radius: 3px 3px 0 0;
cursor: pointer;
font-size: 0.95em;
font-weight: 600;
color: var(--wikt-palette-grey-9, #333);
background: var(--wikt-palette-grey-0, #f8f9fa);
user-select: none;
transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
#wiktku-group-tabs .wiktku-group-tab:hover {
color: var(--wikt-palette-blue-9, #3366cc);
background: var(--wikt-palette-paleblue, #eaf3ff);
}
#wiktku-group-tabs .wiktku-group-tab.wiktku-tab-active {
border-bottom-color: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-white);
background: var(--wikt-palette-blue-9, #3366cc);
font-weight: 700;
}
/* Small dot marking a tab that still has an unfilled required field
hidden inside it (see updateGroupTabIndicators in start.js). Cleared
automatically once that tab becomes the active one. */
#wiktku-group-tabs .wiktku-group-tab.wiktku-tab-missing::after {
content: "";
position: absolute;
top: 4px;
right: -1px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--wikt-palette-red-9, #d33);
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 3px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* Dûnde/Werger/Zarava rows: align the picker's height with the adjacent
word textarea (see .wiktku-start-narrow above). */
.wiktku-lang-row .wiktku-native-select-wrap input.wiktku-ac-input {
height: calc(1.7em + 10px);
min-height: calc(1.7em + 10px);
padding-top: 4px;
padding-bottom: 4px;
}
/* ── Werger / Bi zaravayên din groups: one per gloss, shared spacing ── */
#wiktku-start-trans .wiktku-trans-group,
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.6em;
}
#wiktku-start-trans .wiktku-trans-group:last-child,
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label,
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: 600;
font-size: 0.88em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 3px;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.88em;
margin-top: 3px;
display: block;
/* A native select with no width rule sizes to fit its longest <option>
(here, the longest çavkanî template name) rather than its container,
which overflows the card on a phone — cap it like every other field. */
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* Wide/narrow/medium layout, same idea as MediaWiki:Gadget-wikt.add-
examples.js: rows wrap freely, each sized by its own width tier
(.wiktku-dfn-quote-src-row--wide/narrow/medium) instead of always
stacking one field per line. */
.wiktku-dfn-quote-src-fields {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 6px 10px;
margin-top: 3px;
}
/* Fills the flex row so the manual-source textarea's resize wrapper doesn't shrink to the browser's default width. */
.wiktku-dfn-quote-src-fields > .wiktku-resize-wrap {
flex: 1 1 100%;
width: 100%;
}
/* Stacked layout: textarea gets its own full-width row, button + status
sit below it on their own row. */
.wiktku-dfn-quote-citoid {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.4em;
margin-top: 3px;
margin-bottom: 3px;
}
.wiktku-dfn-quote-citoid-input {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* Column flex + align-items:stretch (above) makes the textarea's
.wiktku-resize-wrap reach full width automatically; button and status
text opt back out via align-self so they keep their natural size. */
.wiktku-dfn-quote-citoid-btn,
.wiktku-dfn-quote-citoid-status {
align-self: flex-start;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.82em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1 1 12em;
}
.wiktku-dfn-quote-src-row--wide {
flex-basis: 100%;
}
.wiktku-dfn-quote-src-row--narrow {
flex: 0 1 7em;
}
.wiktku-dfn-quote-src-row--medium {
flex: 1 1 12em;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.82em;
white-space: nowrap;
}
.wiktku-dfn-quote-src-input {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
/* Re-asserts against the skin's own mobile-breakpoint heading styles,
which load after ours in the cascade. */
#wiktku-start-form .wiktku-start-step-name {
font-size: 1.05em !important;
margin: 0 0 0.2em !important;
line-height: 1.2 !important;
border: 0 !important;
padding: 0 !important;
}
/* Single column: search box, list, and narrow textareas (Etîket,
Hevmane, Dûnde/Jê/Têkildar/Binêre herwiha word boxes, citation
source fields, …) all use the full width. */
.wiktku-native-select-wrap,
input.wiktku-ac-input,
ul.wiktku-ac-results,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 100%;
max-width: 100%;
}
.wiktku-mod-val-input {
flex: 1 1 auto;
max-width: 100%;
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
font-size: 1em;
}
#wiktku-start-form .wiktku-tewandin-wrap textarea.wiktku-tewandin-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
/* Mane, Nimûne + Jêgirtin and their tr/t sub-fields: keep at least 4 rows
on mobile too, rather than shrinking further than the desktop size. */
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form textarea.wiktku-dfn-nimune-text,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
#wiktku-start-form .wiktku-dfn-jeder-group textarea.wiktku-dfn-nimune-quote,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
--wiktku-ta-rows: 4;
font-size: 1em;
}
/* Citoid URL/DOI/ISBN box: taller tap target (~2 lines) on mobile,
and now that this field is a <textarea> (see start.js) its
placeholder can actually wrap into that space — a plain <input>'s
placeholder can never wrap, no matter its height. */
.wiktku-dfn-quote-citoid-input {
min-height: calc(2 * 1.35em + 10px);
font-size: 1em;
}
.wiktku-dfn-nimune-sub,
.wiktku-dfn-quote-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-pron-wrap .wiktku-native-select-wrap input.wiktku-ac-input,
.wiktku-etym-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
font-size: 1em;
min-height: 2.2em;
}
/* Every field stacks full-width on mobile, regardless of its wide/
narrow/medium tier — there's no room to wrap several per line. */
#wiktku-start-form .wiktku-dfn-quote-src-row {
flex-basis: 100%;
}
.wiktku-start-step {
padding: 0.5em 0.6em;
}
}
/* ===================================================================
Night mode
Palette.css already flips --wikt-palette-* vars for night mode; this
hook only covers real colours (panel border) that aren't a palette var,
since a drop shadow is invisible against an equally dark page.
=================================================================== */
@media screen {
html.skin-theme-clientpref-night .wiktku-ac-results {
/* Border stands in for the shadow, invisible on a dark page. */
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
html.skin-theme-clientpref-night .wiktku-start-step,
html.skin-theme-clientpref-night .wiktku-start-step:hover,
html.skin-theme-clientpref-night .wiktku-start-step:focus-within {
box-shadow: none;
}
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
html.skin-theme-clientpref-os .wiktku-start-step,
html.skin-theme-clientpref-os .wiktku-start-step:hover,
html.skin-theme-clientpref-os .wiktku-start-step:focus-within {
box-shadow: none;
}
}
/* "Zimanek lê zêde bike" portlet icon (Minerva/mobile) — Feather-core-edit.svg
via CSS mask, matching Minerva's own icon pattern. */
.minerva-icon-portletlink-ca-wiktku-addlang {
-webkit-mask-image: url("https://upload.wikimedia.org/wikipedia/commons/d/d7/Feather-core-edit.svg");
mask-image: url("https://upload.wikimedia.org/wikipedia/commons/d/d7/Feather-core-edit.svg");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: 20px;
mask-size: 20px;
background-color: currentColor;
background-image: none;
}
/* ── posparams step (live wikitext + helper widgets) ── */
.wiktku-posparams-wrap {
display: flex;
flex-direction: column;
gap: 6px;
}
.wiktku-posparams-helpers {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-posparams-row {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.wiktku-posparams-row label {
font-size: 1em;
min-width: 10em;
padding-right: 0;
}
.wiktku-posparams-subheading {
font-weight: 600;
font-size: 0.88em;
margin-top: 6px;
}
.wiktku-posparams-pronoun-hint {
font-size: 0.9em;
color: var(--wikt-ink-2);
font-style: italic;
}
/* Keeps a pronoun hint and its box on one line, immediately next to each
other, even when the row around them stacks label-over-field (see the
desktop grid layout for .wiktku-posparams-helpers below). */
.wiktku-posparams-field-line {
display: flex;
align-items: center;
gap: 6px;
}
.wiktku-posparams-text {
width: 100%;
box-sizing: border-box;
font-family: monospace;
font-size: 0.9em;
}
.wiktku-posparams-opt-toggle {
font-size: 0.85em;
margin-top: 2px;
display: inline-block;
}
.wiktku-posparams-opt-panel {
display: flex;
flex-direction: column;
gap: 2px;
padding: 4px 0 2px 1em;
}
.wiktku-posparams-opt-row {
display: flex;
align-items: center;
gap: 4px;
font-size: 0.85em;
}
/* ── resim step (one or more images) ── */
.wiktku-resim-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.wiktku-resim-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-resim-item .wiktku-posparams-row {
flex-direction: column;
align-items: flex-start;
gap: 2px;
}
.wiktku-resim-item .wiktku-posparams-row label {
min-width: 0;
font-size: 0.9em;
}
/* ── dfn: "hide/show all subsections" link ──
Toggles a class on the whole dfn stepDiv so one click hides the
etîket/kategorî/hevmane/dijmane/nimûne/jêgirtin/etc. subgroups
across every sense block at once. */
.wiktku-dfn-subs-toggle {
font-size: 0.85em;
font-weight: 500;
display: inline-block;
margin: 2px 0 8px;
color: var(--wikt-palette-blue-9, #3366cc);
text-decoration: none;
}
.wiktku-dfn-subs-toggle:hover {
text-decoration: underline;
}
/* Only the ADVANCED subgroups collapse: Nimûne, Jêgirtin, Hevmane and
Dijmane stay open because they are what people come here to fill in. */
#wiktku-start-form li.wiktku-sense-adv-hidden .wiktku-dfn-subgroup-adv {
display: none !important;
}
/* ── "Malûmatên bikaranînê" accordion — fieldset-style: a legend that
floats on the box's top border (negative translateY + card-color
background "cutting through" the border line), collapsed by default.
See buildSenseBlock in start.js. ── */
#wiktku-start-form .wiktku-sense-adv-box {
margin-top: 14px;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-card, 12px);
padding: 0 16px 16px;
}
#wiktku-start-form .wiktku-sense-adv-legend {
display: inline-flex;
align-items: center;
gap: 8px;
transform: translateY(-10px);
background: var(--wikt-card);
padding: 0 9px;
margin-left: 4px;
}
#wiktku-start-form .wiktku-sense-adv-diamond {
font-size: 12px;
color: var(--wikt-muted);
}
#wiktku-start-form .wiktku-sense-adv-label {
font-size: 12.5px;
font-weight: 500;
color: var(--wikt-ink-2);
}
#wiktku-start-form .wiktku-sense-adv-chevron {
width: 20px;
height: 20px;
border-radius: 6px;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-2);
font-size: 10px;
display: grid;
place-items: center;
background: var(--wikt-card-sunken);
cursor: pointer;
padding: 0;
}
#wiktku-start-form .wiktku-sense-adv-chevron:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
}
/* Collapsed-only summary: a dim one-line status, so glancing at a closed
accordion still says whether there's anything back there. Hidden while
expanded — the real fields (inside .wiktku-dfn-subgroup-adv, toggled by
li.wiktku-sense-adv-hidden above) take its place. */
#wiktku-start-form .wiktku-sense-adv-summary {
display: none;
align-items: center;
gap: 9px;
}
#wiktku-start-form li.wiktku-sense-adv-hidden .wiktku-sense-adv-summary {
display: flex;
}
#wiktku-start-form .wiktku-sense-adv-summary-dots {
letter-spacing: .18em;
color: var(--wikt-muted);
}
#wiktku-start-form .wiktku-sense-adv-summary-text {
font-size: 13.5px;
color: var(--wikt-muted);
}
/* ===================================================================
Desktop text bump
Modifier pills/buttons and posparams helper text read fine at their
small mobile sizes but look too small on desktop, so bump them up
past the 720px breakpoint only.
=================================================================== */
@media (min-width: 721px) {
.wiktku-mod-btn,
.wiktku-mod-confirm,
.wiktku-mod-cancel {
width: 1.8em;
height: 1.8em;
font-size: 1.1em;
}
.wiktku-mod-pill {
font-size: 0.95em;
padding: 3px 10px;
}
.wiktku-mod-val-input {
font-size: 0.95em;
}
.wiktku-posparams-row label {
font-size: 1em;
}
.wiktku-posparams-text {
font-size: 1em;
}
.wiktku-posparams-opt-toggle {
font-size: 0.95em;
}
.wiktku-posparams-opt-row {
font-size: 0.95em;
}
.wiktku-dfn-subs-toggle {
font-size: 0.95em;
}
/* Multi-column card layout for posparams helper fields (transliteration,
gendered forms, comparative/superlative, hanja, verb-tense roots, …),
desktop-only. Rows are shown/hidden via jQuery inline display; the
grid only rearranges visible rows, not which ones are visible. */
.wiktku-posparams-helpers {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 10px 16px;
align-items: start;
}
.wiktku-posparams-helpers > .wiktku-posparams-row {
flex-direction: column;
align-items: stretch;
flex-wrap: nowrap;
gap: 3px;
}
.wiktku-posparams-helpers > .wiktku-posparams-row label {
min-width: 0;
}
.wiktku-posparams-helpers > .wiktku-posparams-row .wiktku-posparams-field,
.wiktku-posparams-helpers > .wiktku-posparams-row select.wiktku-posparams-cinsiyet-sel,
.wiktku-posparams-helpers > .wiktku-posparams-row select.wiktku-posparams-lektur-sel {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* The "Jimar" row lives here too — just another .wiktku-posparams-row,
no extra CSS needed. */
/* These two aren't per-field rows — span the full card width so the
collapsed toggle and its checkbox list don't get squeezed into one
narrow column. */
.wiktku-posparams-helpers > .wiktku-posparams-opt-toggle,
.wiktku-posparams-helpers > .wiktku-posparams-opt-panel,
.wiktku-posparams-helpers > .wiktku-posparams-subheading {
grid-column: 1 / -1;
}
}
/* ===================================================================
SÊWIRANA NÛ — design system layer
===================================================================
Purely the visual skin (colour, type, weight, radius, spacing), layered
after the layout rules above so it wins the cascade.
No @import of Spectral / IBM Plex from Google Fonts: Wikimedia's CSP
blocks third-party font hosts, so the stacks below name the spec's
faces first (used if the wiki installs them locally) and fall back to
faces with the same feel.
Accent colour is reserved for actionable elements (focus, links, the
active tab, the primary button, chips); section headings are near-black.
=================================================================== */
#wiktku-start-form {
/* Surfaces */
--wikt-page: #f4efe5;
--wikt-card: #fffdf8;
--wikt-card-sunken: #fbf8f1;
--wikt-rule: #ded6c8; /* structural hairline */
--wikt-rule-soft: #efe8db; /* inside a card */
--wikt-field-edge: #cfc7b8;
--wikt-field-bg: #fff;
--wikt-code-bg: #f7f4ec;
--wikt-card-edge: #e6dfd2;
/* Ink */
--wikt-ink: #1c1a17;
--wikt-ink-2: #3d3830;
--wikt-ink-3: #57514a;
--wikt-muted: #8a8378;
--wikt-placeholder: #b3ab9e;
/* Accent + status */
--wikt-accent: #2a5db0;
--wikt-accent-deep: #22406f;
--wikt-accent-tint: #eef2fa;
--wikt-accent-edge: #d5def0;
--wikt-danger: #9c3a28;
--wikt-on-accent: #fff; /* text on a filled accent button */
--wikt-link-under: rgba(42, 93, 176, 0.35);
/* Shape */
--wikt-r-field: 10px;
--wikt-r-card: 14px;
--wikt-r-pill: 999px;
--wikt-tap: 44px; /* minimum field height */
--wikt-serif: Spectral, "Iowan Old Style", Georgia, "Times New Roman", serif;
--wikt-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
--wikt-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-family: var(--wikt-sans);
font-size: 15px;
line-height: 1.55;
color: var(--wikt-ink);
background: var(--wikt-page);
padding: 24px;
border-radius: var(--wikt-r-card);
}
/* ── Cards ───────────────────────────────────────────────────────────
Flat cream panel held by a single hairline border, no shadow. */
#wiktku-start-form .wiktku-start-step {
background: var(--wikt-card);
border: 1px solid var(--wikt-card-edge);
border-left: 1px solid var(--wikt-card-edge);
border-radius: var(--wikt-r-card);
box-shadow: none;
padding: 24px 24px 20px;
margin-bottom: 16px;
transition: border-color 0.12s ease;
}
#wiktku-start-form .wiktku-start-step:hover,
#wiktku-start-form .wiktku-start-step:focus-within {
box-shadow: none;
border-color: var(--wikt-rule);
}
/* The fused pos + posparams pair keeps reading as one card. margin-bottom
has to be reset here too — .wiktku-start-step's own 16px otherwise wins
(higher specificity than the older #wiktku-start-pos-only rule this was
meant to override), leaving a visible gap between the two "halves". */
#wiktku-start-form #wiktku-start-pos {
border-radius: var(--wikt-r-card) var(--wikt-r-card) 0 0;
border-bottom: none;
padding-bottom: 8px;
margin-bottom: 0;
}
#wiktku-start-form #wiktku-start-posparams {
border-radius: 0 0 var(--wikt-r-card) var(--wikt-r-card);
border-top: none;
padding-top: 4px;
}
/* ── Type ────────────────────────────────────────────────────────────
Step headings: serif, regular weight, near-black. */
#wiktku-start-form .wiktku-start-step-name {
font-family: var(--wikt-serif);
font-size: 24px !important;
font-weight: 400;
letter-spacing: -0.01em;
line-height: 1.15;
color: var(--wikt-ink);
margin: 0 0 12px;
}
/* display:block matters: this is a <label>, so it was sharing a line with
the control it introduces wherever start.js didn't emit a <br>. */
#wiktku-start-form .wiktku-start-step-question {
display: block;
font-size: 15px;
font-weight: 400;
color: var(--wikt-ink-3);
margin: 0 0 12px;
line-height: 1.6;
max-width: 62ch;
}
/* Collapses the label entirely when empty (posparams' "Cins/zayend" title
can be blank) instead of leaving its margin behind as a gap; a no-op
for every other step, whose question text is never empty. */
#wiktku-start-form .wiktku-start-step-question:empty {
display: none;
margin: 0;
}
/* Same reason — a field label belongs above its field, not beside it. */
#wiktku-start-form .wiktku-posparams-row label,
#wiktku-start-form .wiktku-dfn-sublabel {
display: block;
margin-bottom: 6px;
padding-right: 0;
}
/* Field labels: small, medium weight, full-strength ink — they label a
control, so they should not read as body copy. */
#wiktku-start-form .wiktku-start-step label,
#wiktku-start-form .wiktku-posparams-row label,
#wiktku-start-form .wiktku-dfn-sublabel {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink);
letter-spacing: 0.01em;
}
/* The parenthetical/example half of a label is supporting text. */
#wiktku-start-form .wiktku-dfn-sublabel-hint,
#wiktku-start-form .wiktku-step-hint {
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
font-size: 12.5px;
}
/* ── Fields ──────────────────────────────────────────────────────────
One shape for every control: 1px edge, 10px radius, 44px tall. */
#wiktku-start-form input[type="text"],
#wiktku-start-form input[type="search"],
#wiktku-start-form input.wiktku-ac-input,
#wiktku-start-form textarea,
#wiktku-start-form select {
font-family: var(--wikt-sans);
font-size: 15px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
padding: 10px 14px;
box-shadow: none;
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
#wiktku-start-form input[type="text"],
#wiktku-start-form input[type="search"],
#wiktku-start-form select {
min-height: var(--wikt-tap);
}
#wiktku-start-form textarea {
line-height: 1.6;
}
/* Wikitext and anything template-shaped reads as code. */
#wiktku-start-form .wiktku-posparams-text,
#wiktku-start-form .wiktku-pron-text,
#wiktku-start-form .wiktku-etym-text,
#wiktku-start-form .wiktku-cavkani-text,
#wiktku-start-form .wiktku-tewandin-text,
#wiktku-start-form .wiktku-pron-preview-body {
font-family: var(--wikt-mono);
font-size: 13.5px;
background: var(--wikt-code-bg);
border-color: var(--wikt-card-edge);
}
#wiktku-start-form ::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
/* Focus: accent edge + 3px tint halo. The only "elevation" in the design. */
#wiktku-start-form input[type="text"]:focus,
#wiktku-start-form input[type="search"]:focus,
#wiktku-start-form textarea:focus,
#wiktku-start-form select:focus,
#wiktku-start-form input.wiktku-ac-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The searchable picker's visible input is the field, so it takes the
field shape too (it otherwise inherits an older, boxier rule). */
#wiktku-start-form input.wiktku-ac-input {
min-height: var(--wikt-tap);
border-radius: var(--wikt-r-field);
border-color: var(--wikt-field-edge);
font-size: 15px;
}
#wiktku-start-form .wiktku-ac-results {
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
background: var(--wikt-card);
box-shadow: none;
}
#wiktku-start-form .wiktku-ac-results li.wiktku-ac-active,
#wiktku-start-form .wiktku-ac-results li:hover {
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
}
/* ── Tabs → the accent's main job ────────────────────────────────────
Active tab: tint fill + 3px accent underline, per the spec's
"xala hûrgulî" note. Inactive tabs are plain text. */
#wiktku-start-form #wiktku-group-tabs,
#wiktku-start-form #wiktku-section-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
align-items: center;
border-bottom: 1px solid var(--wikt-rule);
margin-bottom: 16px;
padding-bottom: 0;
}
/* Both bars are direct children of #wiktku-start-form, so a sticky child
stays stuck for as long as any part of the form is in view. Background
is opaque so content scrolling underneath doesn't show through.
Section-tabs pins below --wiktku-siteheader-h — an offset for Vector
2022's own sticky header, which otherwise sits on top of it at top:0
(Minerva has no such header, so mobile needs no offset); group-tabs
pins below that plus --wiktku-stabs-h (section-tabs' own height). Both
custom properties are set by syncStickyTabsOffset() in start.js and
fall back to 0 on skins without a Vector sticky header. */
#wiktku-start-form #wiktku-section-tabs {
position: sticky;
top: var(--wiktku-siteheader-h, 0px);
z-index: 20;
background: var(--wikt-card);
}
#wiktku-start-form #wiktku-group-tabs {
position: sticky;
top: calc(var(--wiktku-siteheader-h, 0px) + var(--wiktku-stabs-h, 0px));
z-index: 19;
background: var(--wikt-card);
}
#wiktku-start-form .wiktku-group-tab,
#wiktku-start-form .wiktku-section-tab {
font-size: 13.5px;
font-weight: 500;
color: var(--wikt-ink-3);
background: transparent;
border: 0;
border-bottom: 3px solid transparent;
border-radius: var(--wikt-r-pill) var(--wikt-r-pill) 0 0;
padding: 9px 14px 11px;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease;
}
#wiktku-start-form .wiktku-group-tab:hover,
#wiktku-start-form .wiktku-section-tab:hover {
background: var(--wikt-card-sunken);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-group-tab.wiktku-tab-active,
#wiktku-start-form .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
/* ── Progressive-disclosure toggles ──────────────────────────────────
Shared by "Tercîhên pêşketî" (posparams) and "Malûmatên pêşketî"
(Mane): a rotating chevron plus a count of what's hidden. */
#wiktku-start-form .wiktku-adv-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin: 4px 0 12px;
padding-top: 16px;
border-top: 1px solid var(--wikt-rule-soft);
}
#wiktku-start-form .wiktku-adv-toggle {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 14px;
font-weight: 500;
color: var(--wikt-accent);
text-decoration: none;
cursor: pointer;
}
#wiktku-start-form .wiktku-adv-toggle:hover .wiktku-adv-toggle-text {
text-decoration: underline;
}
#wiktku-start-form .wiktku-adv-toggle-chevron {
width: 22px;
height: 22px;
flex: 0 0 22px;
display: grid;
place-items: center;
font-size: 11px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
border-radius: 6px;
transform: rotate(-90deg);
transition: transform 0.15s ease;
}
/* Open state: dfn flips the class on the step, posparams on the link. */
#wiktku-start-form .wiktku-adv-toggle.wiktku-adv-open .wiktku-adv-toggle-chevron,
#wiktku-start-dfn:not(.wiktku-dfn-subs-hidden) .wiktku-adv-toggle-chevron {
transform: rotate(0deg);
}
#wiktku-start-form .wiktku-posparams-adv-panel {
border: 1px solid var(--wikt-rule-soft);
border-radius: 12px;
background: var(--wikt-card-sunken);
padding: 20px 18px;
margin-bottom: 4px;
}
/* Vertical rhythm between stacked field rows (e.g. "Tewandina lêkerê"),
wider than a row's own label-to-field gap so rows read as distinct
groups instead of bleeding together. */
#wiktku-start-form .wiktku-posparams-helpers {
display: flex;
flex-direction: column;
gap: 14px;
}
/* ── posparams grid ──────────────────────────────────────────────────
"Tor, ne rêz" — two columns, nothing narrower than 220px, so the six
gender/number fields stop being a six-across scanline. */
@media (min-width: 721px) {
#wiktku-start-form .wiktku-posparams-helpers,
#wiktku-start-form .wiktku-posparams-adv-panel {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 18px 22px;
align-items: start;
}
#wiktku-start-form .wiktku-posparams-adv-panel > .wiktku-posparams-row,
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-posparams-row {
flex-direction: column;
align-items: stretch;
flex-wrap: nowrap;
gap: 7px;
}
/* The row's own gap already separates the label from its field — the
label's margin-bottom (set for the stacked mobile layout) would
otherwise double that spacing here. */
#wiktku-start-form .wiktku-posparams-adv-panel > .wiktku-posparams-row label,
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-posparams-row label {
margin-bottom: 0;
}
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-adv-toggle-row,
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-posparams-adv-panel,
#wiktku-start-form .wiktku-posparams-adv-panel > .wiktku-posparams-opt-panel {
grid-column: 1 / -1;
}
}
#wiktku-start-form .wiktku-posparams-opt-panel {
border-top: 1px solid var(--wikt-rule-soft);
padding-top: 14px;
margin-top: 4px;
}
#wiktku-start-form .wiktku-posparams-opt-row {
font-size: 13px;
color: var(--wikt-ink-3);
}
/* ── Sense blocks ────────────────────────────────────────────────────
Each sense is a sunken panel inside the Mane card, with its number as
an accent counter — so "how many meanings am I writing" is legible at
a glance. */
#wiktku-start-form .wiktku-dfn-subgroup {
margin: 14px 0 0;
padding-top: 14px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* No divider above the first item — it would be a rule against the
sense's own text. Needed explicitly: the earlier :first-of-type rule
is a class selector and loses to this id-scoped one. */
#wiktku-start-form .wiktku-dfn-subgroup:first-of-type {
margin-top: 8px;
padding-top: 0;
border-top: none;
}
#wiktku-start-form .wiktku-dfn-sublabel-term {
color: var(--wikt-ink);
}
/* Plain white field like any other, not a tinted fill — a tint would
read as "disabled" next to the white Mane box above it. */
#wiktku-start-form .wiktku-dfn-subfield,
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form .wiktku-word-input {
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
/* No "1." "2." markers on the sub-lists: numbered rows would read as a
form to fill top-down, when each row is really "another one if you
have one" — and the marker would land on its own line above the
block-level textarea, looking like a stray character. */
#wiktku-start-form .wiktku-dfn-sublist {
margin: 0;
padding-left: 0;
list-style: none;
}
#wiktku-start-form .wiktku-dfn-sublist > li {
margin-bottom: 6px;
}
/* ── Chips: tags, modifiers, template pills ──────────────────────────── */
#wiktku-start-form .wiktku-mod-pill,
#wiktku-start-form .wiktku-besendin-select + .wiktku-chip {
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 13px;
padding: 6px 12px;
}
#wiktku-start-form .wiktku-mod-btn,
#wiktku-start-form .wiktku-mod-confirm,
#wiktku-start-form .wiktku-mod-cancel {
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-radius: 8px;
}
/* ── Chip + toggle fields ("1c" design) — Dever/Şîve, Etîket, Kategorî:
a small "+ …" toggle on the sense, not an always-visible field (see
buildChipField in start.js, used by all three). Chips sit inline before
the toggle; clicking it opens a compact panel with optional quick-pick
suggestions plus free text below. Each field's own wrap class (e.g.
.wiktku-dfn-dever-wrap) only scopes chip reads/writes in start.js — all
the visual styling here is shared via .wiktku-dfn-chipfield-*. The three
toggles live together in .wiktku-dfn-chipfield-row; their panels render
separately in .wiktku-dfn-chipfield-panels below it, so opening one
field's panel never pushes the other toggles around. ── */
#wiktku-start-form .wiktku-dfn-chipfield-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin: 8px 0;
}
#wiktku-start-form .wiktku-dfn-chipfield-panels {
display: flex;
flex-direction: column;
gap: 8px;
}
#wiktku-start-form .wiktku-dfn-chipfield-wrap {
margin: 0;
}
#wiktku-start-form .wiktku-dfn-chipfield-chips {
display: inline-flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
#wiktku-start-form .wiktku-dfn-chipfield-chip {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
#wiktku-start-form .wiktku-dfn-chipfield-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
#wiktku-start-form .wiktku-dfn-chipfield-remove:hover {
color: var(--wikt-danger);
}
#wiktku-start-form .wiktku-dfn-chipfield-toggle {
font-size: 12px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
padding: 3px 10px;
cursor: pointer;
}
#wiktku-start-form .wiktku-dfn-chipfield-toggle:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
#wiktku-start-form .wiktku-dfn-chipfield-panel {
margin-top: 8px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-card);
border-radius: var(--wikt-r-card, 6px);
padding: 12px;
display: flex;
flex-direction: column;
gap: 10px;
/* max-width bounds .wiktku-dfn-chipfield-suggest so its pills (e.g.
Dever/Şîve's 11 suggestions) wrap onto multiple lines instead of
stretching the panel into one long unbroken row. */
min-width: 260px;
max-width: 340px;
}
#wiktku-start-form .wiktku-dfn-chipfield-hint {
font-size: 13px;
color: var(--wikt-ink-2);
}
#wiktku-start-form .wiktku-dfn-chipfield-suggest {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
#wiktku-start-form .wiktku-dfn-chipfield-pill {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
cursor: pointer;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
padding: 3px 8px 3px 10px;
}
#wiktku-start-form .wiktku-dfn-chipfield-pill-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
#wiktku-start-form .wiktku-dfn-chipfield-pill-remove:hover {
color: var(--wikt-danger);
}
#wiktku-start-form .wiktku-dfn-chipfield-pill:hover {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent-edge);
color: var(--wikt-accent-deep);
}
#wiktku-start-form .wiktku-dfn-chipfield-input-row {
display: flex;
gap: 6px;
}
#wiktku-start-form .wiktku-dfn-chipfield-input {
flex: 1;
/* Without this, flexbox gives a text input an automatic minimum size
based on its default content width, which is wider than this panel
ever gets on a phone — the row can't shrink to fit and "Qeyd bike"
gets pushed out past the card's edge instead of wrapping/shrinking. */
min-width: 0;
border: 1px solid var(--wikt-field-edge);
border-radius: 2px;
font-size: 13px;
padding: 6px 8px;
background: var(--wikt-card);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-dfn-chipfield-add {
flex-shrink: 0;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
background: var(--wikt-card);
color: var(--wikt-ink-2);
font-family: var(--wikt-sans);
font-size: 13px;
padding: 6px 14px;
cursor: pointer;
}
#wiktku-start-form .wiktku-dfn-chipfield-add:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
/* ── Footer + buttons ────────────────────────────────────────────────
One primary filled pill; destructive actions are outline-only in the
danger colour so they stay quiet next to it. */
#wiktku-start-form #wiktku-start-footer {
background: var(--wikt-card-sunken);
border-top: 1px solid var(--wikt-rule);
border-radius: 0 0 var(--wikt-r-card) var(--wikt-r-card);
padding: 16px 20px;
gap: 10px;
}
#wiktku-start-form #wiktku-start-footer .oo-ui-buttonElement-button {
border-radius: var(--wikt-r-pill) !important;
font-family: var(--wikt-sans);
font-size: 14px;
font-weight: 500;
padding: 11px 22px;
box-shadow: none !important;
min-height: var(--wikt-tap);
}
#wiktku-start-form #wiktku-unhide-editform .oo-ui-buttonElement-button {
background: var(--wikt-accent) !important;
border-color: var(--wikt-accent) !important;
color: var(--wikt-on-accent) !important;
}
/* Destructive actions: outline, not fill. */
#wiktku-start-form #wiktku-start-footer .oo-ui-flaggedElement-destructive .oo-ui-buttonElement-button {
background: var(--wikt-card) !important;
border-color: var(--wikt-danger) !important;
color: var(--wikt-danger) !important;
}
#wiktku-start-form #wiktku-last-saved {
font-family: var(--wikt-mono);
font-size: 12px;
color: var(--wikt-muted);
}
/* ── Beşên din picker ────────────────────────────────────────────────
This dropdown is now the entry point to eighteen sections, so it gets
room and a label rather than sitting flush under the tab bar. */
#wiktku-start-form .wiktku-besendin-picker {
margin: 0 0 20px;
padding: 16px 18px;
background: var(--wikt-card);
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
}
#wiktku-start-form .wiktku-besendin-picker .wiktku-native-select-wrap,
#wiktku-start-form .wiktku-besendin-picker input.wiktku-ac-input {
width: 100%;
max-width: 28em;
}
/* ── Notices ─────────────────────────────────────────────────────────── */
#wiktku-start-form .wiktku-addlang-notice {
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
color: var(--wikt-ink-2);
padding: 12px 16px;
margin-bottom: 16px;
}
#wiktku-start-form .wiktku-pron-preview {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent-edge);
border-radius: 12px;
}
#wiktku-start-form .wiktku-pron-preview-confirm,
#wiktku-start-form .wiktku-pron-preview-cancel {
border-radius: var(--wikt-r-pill);
padding: 8px 16px;
min-height: 36px;
}
/* Links carry the accent and a soft underline, per the spec's `a` rule. */
#wiktku-start-form a {
color: var(--wikt-accent);
text-decoration: none;
border-bottom: 1px solid var(--wikt-link-under);
}
#wiktku-start-form a:hover {
color: var(--wikt-accent-deep);
}
#wiktku-start-form .wiktku-adv-toggle,
#wiktku-start-form .wiktku-group-tab,
#wiktku-start-form .wiktku-section-tab {
border-bottom-style: solid;
}
#wiktku-start-form .wiktku-adv-toggle {
border-bottom: 0;
}
/* ===================================================================
Dark mode
===================================================================
Every colour above is a token on #wiktku-start-form, so this is a
swap of that one block.
Only applies given an explicit dark signal (skin-theme-clientpref-night /
skin-nightmode, or skin-theme-clientpref-os plus prefers-color-scheme:
dark) — no bare prefers-color-scheme fallback, since legacy Vector sets
no theme class either and would otherwise go dark with nothing else on
the page to match.
Palette is warm, not neutral grey, to keep the cream design's character.
Accent lightens to #7ba7e8 since #2a5db0 on a dark card is ~2.4:1
contrast and reads as near-black.
=================================================================== */
html.skin-theme-clientpref-night #wiktku-start-form,
html.skin-nightmode #wiktku-start-form {
--wikt-page: #171613;
--wikt-card: #201e1a;
--wikt-card-sunken: #1a1916;
--wikt-card-edge: #322e28;
--wikt-rule: #3a3630;
--wikt-rule-soft: #2c2924;
--wikt-field-edge: #4a453d;
--wikt-field-bg: #191815;
--wikt-code-bg: #14130f;
--wikt-ink: #ece7dd;
--wikt-ink-2: #d5cfc4;
--wikt-ink-3: #b3aca1;
--wikt-muted: #8f887c;
--wikt-placeholder: #6d675e;
--wikt-accent: #7ba7e8;
--wikt-accent-deep: #cfe0f8;
--wikt-accent-tint: #1c2839;
--wikt-accent-edge: #35496b;
--wikt-danger: #e5907c;
--wikt-on-accent: #14213a;
--wikt-link-under: rgba(123, 167, 232, 0.4);
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os #wiktku-start-form {
--wikt-page: #171613;
--wikt-card: #201e1a;
--wikt-card-sunken: #1a1916;
--wikt-card-edge: #322e28;
--wikt-rule: #3a3630;
--wikt-rule-soft: #2c2924;
--wikt-field-edge: #4a453d;
--wikt-field-bg: #191815;
--wikt-code-bg: #14130f;
--wikt-ink: #ece7dd;
--wikt-ink-2: #d5cfc4;
--wikt-ink-3: #b3aca1;
--wikt-muted: #8f887c;
--wikt-placeholder: #6d675e;
--wikt-accent: #7ba7e8;
--wikt-accent-deep: #cfe0f8;
--wikt-accent-tint: #1c2839;
--wikt-accent-edge: #35496b;
--wikt-danger: #e5907c;
--wikt-on-accent: #14213a;
--wikt-link-under: rgba(123, 167, 232, 0.4);
}
}
/* A handful of rules still name literal light colours instead of tokens;
neutralise the ones that actually show through in night mode. */
html.skin-theme-clientpref-night #wiktku-start-form .wiktku-ac-results,
html.skin-nightmode #wiktku-start-form .wiktku-ac-results {
background: var(--wikt-card);
border-color: var(--wikt-rule);
color: var(--wikt-ink);
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os #wiktku-start-form .wiktku-ac-results {
background: var(--wikt-card);
border-color: var(--wikt-rule);
color: var(--wikt-ink);
}
}
/* ===================================================================
Autocomplete list: stop it escaping its card
===================================================================
A fixed width would run past the card's border on full-width pickers,
since the list is absolutely positioned at the wrapper's left edge.
width:100% ties it to the (shrink-to-fit) wrapper's input width instead;
min/max-width keep short pickers readable and stop viewport overflow. */
#wiktku-start-form ul.wiktku-ac-results {
width: 100%;
min-width: min(18em, 100%);
max-width: min(92vw, 34em);
box-sizing: border-box;
border-radius: var(--wikt-r-field);
box-shadow: none;
}
/* And the wrapper itself must not exceed the card it sits in. */
#wiktku-start-form .wiktku-native-select-wrap {
max-width: 100%;
}
#wiktku-start-form .wiktku-cavkani-wrap .wiktku-native-select-wrap,
#wiktku-start-form .wiktku-cavkani-wrap input.wiktku-ac-input,
#wiktku-start-form .wiktku-pron-wrap .wiktku-native-select-wrap,
#wiktku-start-form .wiktku-etym-wrap .wiktku-native-select-wrap {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* ===================================================================
Mobile footer
===================================================================
Below 720px the primary button keeps its full size and everything
else shrinks to a secondary scale, so the sticky footer doesn't
dominate a phone viewport.
=================================================================== */
@media (max-width: 720px) {
#wiktku-start-form {
padding: 12px;
font-size: 14px;
}
#wiktku-start-form .wiktku-start-step {
padding: 16px 14px 14px;
border-radius: 12px;
}
#wiktku-start-form .wiktku-start-step-name {
font-size: 20px !important;
}
#wiktku-start-form #wiktku-start-footer {
padding: 10px 12px;
gap: 6px;
row-gap: 8px;
}
#wiktku-start-form #wiktku-start-footer .oo-ui-buttonElement-button {
font-size: 13px;
padding: 8px 14px;
min-height: 38px;
}
/* The one you actually press stays thumb-sized. */
#wiktku-start-form #wiktku-unhide-editform .oo-ui-buttonElement-button {
font-size: 14px;
padding: 10px 20px;
min-height: var(--wikt-tap);
}
/* Secondary actions share a row instead of each claiming a full one. */
#wiktku-start-form #wiktku-start-footer .oo-ui-buttonElement {
flex: 0 1 auto;
}
#wiktku-start-form #wiktku-last-saved {
flex: 1 0 100%;
order: 10;
font-size: 11px;
white-space: normal;
}
#wiktku-start-form .wiktku-adv-toggle-row {
gap: 8px;
}
}
/* ===================================================================
IPA character palette (v3 spec)
===================================================================
Click-to-insert chart under Bilêvkirin, after fr.wiktionary's. Two
sub-tabs — a short Kurmancî set that opens by default, and the full
chart — because ~20 of the ~180 IPA symbols ever occur in Kurmancî
and showing all of them first is the same "wall of fields" problem
this redesign exists to fix.
=================================================================== */
#wiktku-start-form .wiktku-ipa-panel {
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
background: var(--wikt-card-sunken);
padding: 0 0 12px;
margin-bottom: 8px;
overflow: hidden;
}
#wiktku-start-form .wiktku-ipa-subtabs {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px 0;
border-bottom: 1px solid var(--wikt-rule-soft);
margin-bottom: 12px;
flex-wrap: wrap;
}
#wiktku-start-form .wiktku-ipa-subtab {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink-3);
padding: 7px 12px 9px;
border-bottom: 3px solid transparent;
border-radius: 8px 8px 0 0;
cursor: pointer;
}
#wiktku-start-form .wiktku-ipa-subtab:hover {
background: var(--wikt-card);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-ipa-subtab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
/* Templates are not characters, so they sit apart from the chart —
pinned to the right of the sub-tab row. */
#wiktku-start-form .wiktku-ipa-tplbtns {
margin-left: auto;
display: flex;
gap: 6px;
padding-bottom: 6px;
}
#wiktku-start-form .wiktku-ipa-tplbtn {
font-family: var(--wikt-mono);
font-size: 12px;
padding: 6px 10px;
border-radius: 7px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
white-space: nowrap;
}
#wiktku-start-form .wiktku-ipa-tplbtn:hover {
border-color: var(--wikt-accent);
}
#wiktku-start-form .wiktku-ipa-grid {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 12px;
}
#wiktku-start-form .wiktku-ipa-row {
display: flex;
align-items: flex-start;
gap: 10px;
}
#wiktku-start-form .wiktku-ipa-rowlabel {
flex: 0 0 74px;
font-size: 11.5px;
color: var(--wikt-muted);
padding-top: 8px;
line-height: 1.3;
}
#wiktku-start-form .wiktku-ipa-keys {
display: flex;
flex-wrap: wrap;
gap: 4px;
flex: 1 1 auto;
}
/* Full IPA chart: a dropdown picks one group (Bêdeng 1, Bêdeng 2, …)
instead of showing all ~180 symbols' rows at once. */
#wiktku-start-form .wiktku-ipa-group-row {
padding-bottom: 4px;
}
#wiktku-start-form select.wiktku-ipa-group-select {
width: 14em;
max-width: 100%;
}
#wiktku-start-form .wiktku-ipa-key {
width: 30px;
height: 30px;
padding: 0;
border-radius: 8px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-ink);
font-family: var(--wikt-mono);
font-size: 14px;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.1s ease, border-color 0.1s ease;
}
#wiktku-start-form .wiktku-ipa-key:hover {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
#wiktku-start-form .wiktku-ipa-key:active {
transform: translateY(1px);
}
/* Ligatures like d͡ʒ and long vowels need a little more room than one
glyph; let those keys grow instead of clipping. */
#wiktku-start-form .wiktku-ipa-key {
width: auto;
min-width: 30px;
padding: 0 5px;
}
/* A combining mark is shown on a dotted circle so it has something to
attach to; only the mark itself is inserted. */
#wiktku-start-form .wiktku-ipa-key-combining {
color: var(--wikt-ink-2);
}
@media (max-width: 720px) {
#wiktku-start-form .wiktku-ipa-row {
flex-direction: column;
gap: 3px;
}
#wiktku-start-form .wiktku-ipa-rowlabel {
flex: 0 0 auto;
padding-top: 0;
}
#wiktku-start-form .wiktku-ipa-key {
min-width: 34px;
height: 34px;
}
}
/* ── "+ Beş zêde bike" menu ──────────────────────────────────────────
Adding a part of speech and adding Bilêvkirin are different actions,
so the button opens a small menu instead of doing one of them. */
#wiktku-start-form .wiktku-addsection-menu {
position: absolute;
z-index: 60;
margin-top: 6px;
min-width: 15em;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: 10px;
padding: 4px;
display: flex;
flex-direction: column;
}
#wiktku-start-form .wiktku-addsection-item {
display: flex;
flex-direction: column;
gap: 1px;
padding: 8px 10px;
border-radius: 7px;
border-bottom: 0;
text-decoration: none;
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-addsection-item:hover {
background: var(--wikt-accent-tint);
}
#wiktku-start-form .wiktku-addsection-item-label {
font-size: 13.5px;
font-weight: 500;
}
#wiktku-start-form .wiktku-addsection-item-hint {
font-size: 11.5px;
color: var(--wikt-muted);
}
#wiktku-start-form #wiktku-section-tabs .oo-ui-buttonElement {
position: relative;
}
/* Bilêvkirin is a peer of the POS sections but not a part of speech —
a little extra space before it marks the boundary, no divider line. */
#wiktku-start-form .wiktku-section-tab-pron {
margin-left: 6px;
}
/* ===================================================================
Per-sense tabs
===================================================================
"Mane 1 / Mane 2 / + Mane zêde bike" switches which sense is shown;
only the active one is in the flow, so a multi-sense entry doesn't
turn into a long scroll. */
#wiktku-start-form .wiktku-dfn-sense-tabs {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
border-bottom: 1px solid var(--wikt-rule);
margin-bottom: 14px;
}
#wiktku-start-form .wiktku-dfn-sense-tab {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink-3);
background: transparent;
border: 0;
border-bottom: 3px solid transparent;
border-radius: var(--wikt-r-pill) var(--wikt-r-pill) 0 0;
padding: 7px 12px 9px;
cursor: pointer;
user-select: none;
transition: background 0.12s ease, color 0.12s ease;
}
#wiktku-start-form .wiktku-dfn-sense-tab:hover {
background: var(--wikt-card-sunken);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-dfn-sense-tab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
/* Delete button on the Mane / Beş tab bars — a real OOUI ButtonWidget
(icon: "trash", flags: ["destructive"], framed: false), so its glyph,
red destructive tint and hover/focus states all come from OOUI's own
WikimediaUI theme rather than being reimplemented here. This class
only places it: a little breathing room from the tab before it, and
vertically centered in the flex tab bar (OOUI's own icon-button box
is already close to the tabs' height, so no extra sizing is needed). */
#wiktku-start-form .wiktku-trash-btn {
margin-left: 4px;
}
#wiktku-start-form li.wiktku-dfn-sense {
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
background: var(--wikt-card);
margin-bottom: 10px;
padding: 14px;
list-style: none;
overflow: visible;
}
#wiktku-start-form li.wiktku-dfn-sense.wiktku-sense-collapsed {
display: none;
}
/* ===================================================================
Template rows in the pickers (v3 spec)
===================================================================
One row shape everywhere a template is chosen: the ⓘ explains terms
like "guharto/kevnbûyî/ku-Latn/şaşnivîs" without leaving the form,
and the 🔗 links out for when it doesn't.
=================================================================== */
#wiktku-start-form ul.wiktku-ac-results li.wiktku-ac-tplrow {
display: flex;
align-items: center;
gap: 8px;
min-height: 44px;
white-space: normal;
padding: 6px 10px;
}
#wiktku-start-form .wiktku-ac-tpltext {
flex: 1 1 auto;
min-width: 0;
font-family: var(--wikt-mono);
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Status, not category: green is the one to reach for, yellow means
"only if you specifically need it" (kevnbûyî, şaşnivîs, kêmpeyda). */
#wiktku-start-form .wiktku-tpl-dot {
flex: 0 0 8px;
width: 8px;
height: 8px;
border-radius: 999px;
}
#wiktku-start-form .wiktku-tpl-dot-ok { background: #3d8a55; }
#wiktku-start-form .wiktku-tpl-dot-special { background: #c9971f; }
#wiktku-start-form .wiktku-tpl-icons {
flex: 0 0 auto;
display: flex;
gap: 5px;
align-items: center;
}
#wiktku-start-form .wiktku-tpl-icon {
width: 26px;
height: 26px;
display: grid;
place-items: center;
font-size: 12px;
line-height: 1;
cursor: pointer;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom: 1px solid var(--wikt-accent-edge);
text-decoration: none;
padding: 0;
}
#wiktku-start-form .wiktku-tpl-icon-link { border-radius: 7px; }
#wiktku-start-form .wiktku-tpl-icon-info {
border-radius: 999px;
font-family: var(--wikt-serif);
font-style: italic;
font-weight: 600;
background: transparent;
border-color: var(--wikt-field-edge);
color: var(--wikt-muted);
}
#wiktku-start-form .wiktku-tpl-icon:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
/* ── The ⓘ panel ─────────────────────────────────────────────────────
Three parts, in the spec's order: what it does, its parameters, and a
way out to the template page. */
#wiktku-start-form .wiktku-tpl-info {
position: relative;
margin: 8px 0 4px;
padding: 14px 16px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
border-radius: 12px;
font-size: 13.5px;
line-height: 1.6;
color: var(--wikt-ink-2);
}
#wiktku-start-form .wiktku-tpl-info-name {
font-family: var(--wikt-mono);
font-size: 13px;
color: var(--wikt-accent-deep);
margin-bottom: 6px;
}
#wiktku-start-form .wiktku-tpl-info-params {
margin-top: 8px;
}
#wiktku-start-form .wiktku-tpl-info-params-title {
font-size: 12px;
color: var(--wikt-muted);
margin-bottom: 2px;
}
#wiktku-start-form .wiktku-tpl-info-params ul {
margin: 0;
padding-left: 18px;
font-family: var(--wikt-mono);
font-size: 12.5px;
}
#wiktku-start-form .wiktku-tpl-info-link {
display: inline-block;
margin-top: 10px;
font-size: 13px;
}
#wiktku-start-form .wiktku-tpl-info-close {
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
border-radius: 999px;
border: 0;
background: transparent;
color: var(--wikt-muted);
cursor: pointer;
font-size: 13px;
line-height: 1;
}
#wiktku-start-form .wiktku-tpl-info-close:hover {
background: var(--wikt-card);
color: var(--wikt-ink);
}
/* ===================================================================
Footer overflow "⋯" (v3 spec)
===================================================================
Bişîne and "Edîtora çavkaniyê veke" stay on the bar; the cache
actions, saved-at note and Tercîh move in here so the destructive
buttons don't compete for attention and the sticky footer stays
to one row on a phone.
=================================================================== */
#wiktku-start-form .wiktku-more-wrap {
position: relative;
margin-left: auto;
flex: 0 0 auto;
}
#wiktku-start-form .wiktku-more-btn {
width: 36px;
height: 36px;
border-radius: 999px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-card);
color: var(--wikt-muted);
font-size: 16px;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
}
#wiktku-start-form .wiktku-more-btn:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
}
#wiktku-start-form .wiktku-more-menu {
position: absolute;
right: 0;
bottom: calc(100% + 8px);
z-index: 70;
min-width: 17em;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: 12px;
padding: 6px;
display: flex;
flex-direction: column;
gap: 2px;
}
#wiktku-start-form .wiktku-more-item {
display: flex;
align-items: center;
gap: 8px;
}
/* The buttons inside the menu are menu rows, not buttons on a bar. */
#wiktku-start-form .wiktku-more-item .oo-ui-buttonElement {
flex: 1 1 auto;
}
#wiktku-start-form .wiktku-more-item .oo-ui-buttonElement-button {
display: block;
width: 100%;
text-align: left;
border: 0 !important;
background: transparent !important;
border-radius: 7px !important;
padding: 9px 10px;
min-height: 0;
font-weight: 400;
color: var(--wikt-ink) !important;
}
#wiktku-start-form .wiktku-more-item .oo-ui-buttonElement-button:hover {
background: var(--wikt-accent-tint) !important;
}
#wiktku-start-form .wiktku-more-item-danger .oo-ui-buttonElement-button {
color: var(--wikt-danger) !important;
}
#wiktku-start-form .wiktku-more-item-prefs .wiktku-prefs-btn {
width: 26px;
height: 26px;
flex: 0 0 26px;
border: 0;
background-color: transparent;
}
#wiktku-start-form .wiktku-more-item-label {
font-size: 13.5px;
}
#wiktku-start-form .wiktku-more-sep {
height: 1px;
background: var(--wikt-rule-soft);
margin: 4px 2px;
}
#wiktku-start-form .wiktku-more-note {
padding: 4px 10px 2px;
}
#wiktku-start-form .wiktku-more-note #wiktku-last-saved {
white-space: normal;
}
/* "+ Mane zêde bike" sits as the last item in .wiktku-dfn-sense-tabs. */
#wiktku-start-form .wiktku-add-sense {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 3px 0;
padding: 6px 14px;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
background: var(--wikt-card);
color: var(--wikt-ink-2);
font-family: var(--wikt-sans);
font-size: 13px;
cursor: pointer;
min-height: 34px;
}
#wiktku-start-form .wiktku-add-sense:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
#wiktku-start-form .wiktku-add-sense-plus {
color: var(--wikt-accent);
font-size: 15px;
line-height: 1;
}
/* "+ Beşeke din zêde bike" and "Bi otomatîk tijî bike" are OO.ui.ButtonWidgets
(see start.js), which otherwise render with OOUI's own framed chrome —
override the inner .oo-ui-buttonElement-button to match .wiktku-add-sense's
flat-pill shape above instead, same !important pattern already used for
#wiktku-start-footer's buttons. */
#wiktku-start-form .wiktku-add-sense-btn .oo-ui-buttonElement-button {
border: 1px solid var(--wikt-field-edge) !important;
border-radius: var(--wikt-r-pill) !important;
background: var(--wikt-card) !important;
color: var(--wikt-ink-2) !important;
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: normal;
padding: 6px 14px;
min-height: 34px;
box-shadow: none !important;
}
#wiktku-start-form .wiktku-add-sense-btn .oo-ui-buttonElement-button:hover {
border-color: var(--wikt-accent) !important;
color: var(--wikt-accent) !important;
background: var(--wikt-accent-tint) !important;
}
/* ── Şablon picker, demoted to a link ────────────────────────────────
Picking a template replaces the definition rather than adding to it,
so it sits above the Mane box as a quiet alternative route, hidden
behind one line of text until it is actually wanted. */
#wiktku-start-form .wiktku-dfn-tpl-link {
display: inline-block;
margin-bottom: 8px;
font-size: 12.5px;
color: var(--wikt-muted);
border-bottom: 1px solid transparent;
}
#wiktku-start-form .wiktku-dfn-tpl-link:hover {
color: var(--wikt-accent);
border-bottom-color: var(--wikt-link-under);
}
#wiktku-start-form .wiktku-dfn-tpl-row {
margin-bottom: 10px;
padding-bottom: 12px;
border-bottom: 1px solid var(--wikt-rule-soft);
}
/* ── Template-row icons on touch screens ─────────────────────────────
26px is fine for a mouse and too small for a thumb, and these two sit
right next to each other at the end of a row you can also tap to
select. Give them real targets and some separation on small screens. */
@media (max-width: 720px), (pointer: coarse) {
#wiktku-start-form .wiktku-tpl-icon {
width: 34px;
height: 34px;
font-size: 14px;
}
#wiktku-start-form .wiktku-tpl-icons {
gap: 8px;
padding-left: 4px;
}
#wiktku-start-form ul.wiktku-ac-results li.wiktku-ac-tplrow {
min-height: 50px;
}
}
/* The rendered pronunciation preview is real parser output, so it needs
to sit as content rather than in the monospace wikitext treatment the
panel inherits. */
#wiktku-start-form .wiktku-pron-preview-body {
font-family: var(--wikt-sans);
font-size: 15px;
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: 8px;
padding: 10px 12px;
white-space: normal;
}
#wiktku-start-form .wiktku-pron-preview-body .IPA,
#wiktku-start-form .wiktku-pron-preview-body code,
#wiktku-start-form .wiktku-pron-preview-body tt {
font-family: var(--wikt-mono);
}
/* Parser output brings its own margins; strip the outer ones so the
preview box doesn't gain a blank first line. */
#wiktku-start-form .wiktku-pron-preview-body > *:first-child { margin-top: 0; }
#wiktku-start-form .wiktku-pron-preview-body > *:last-child { margin-bottom: 0; }
oyj3atlkkrmnfj3ryqa6b13omt8j8jg
6334329
6334322
2026-08-17T14:22:22Z
Wikihez
7344
6334329
css
text/css
/* ===================================================================
#editform while the wizard is active
=================================================================== */
/* Keeps #editform genuinely laid out instead of display:none*/
.wiktku-offscreen {
position: absolute !important;
left: -9999px !important;
top: 0 !important;
width: 100%;
max-width: 100%;
visibility: hidden;
}
/* ===================================================================
Base scale
=================================================================== */
#wiktku-start-form {
font-size: 0.92em;
line-height: 1.35;
}
#wiktku-start-form * {
box-sizing: border-box;
}
.wiktku-addlang-notice {
margin: 0 0 0.8em;
padding: 0.6em 0.8em;
background: var(--wikt-palette-paleblue);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 4px;
}
/* ===================================================================
Step cards
a compact card with a left accent bar, so each step reads as
a distinct unit
=================================================================== */
.wiktku-start-step {
padding: 0.7em 0.9em;
margin-bottom: 0.6em;
border: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
border-left: 4px solid var(--wikt-palette-blue-9, #3366cc);
border-radius: 6px;
background: var(--wikt-palette-white);
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
transition: border-left-color 0.15s ease, box-shadow 0.15s ease;
}
.wiktku-start-step:hover {
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
}
.wiktku-start-step:focus-within {
border-left-color: var(--wikt-palette-blue-9, #3366cc);
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
/* Fuses the posparams card onto the bottom of the pos card (shared left
accent bar, no border/gap) since posparams always renders directly
after #wiktku-start-pos once a pos is chosen. */
#wiktku-start-pos {
border-radius: 6px 6px 0 0;
margin-bottom: 0;
border-bottom: none;
}
#wiktku-start-posparams {
border-radius: 0 0 6px 6px;
border-top: none;
padding-top: 0.3em;
}
#wiktku-start-posparams .wiktku-start-step-name {
display: none;
}
/* Scoped and !important: this is a real <h3> tag (see start.js), so the
skin's default heading styles would otherwise override a plain class
rule. Coloured to match the card's accent bar; .wiktku-start-step-question
below stays lighter/muted so the two don't compete. */
#wiktku-start-form .wiktku-start-step-name {
font-size: 1.1em !important;
font-weight: 700;
letter-spacing: 0.03em;
margin: 0 0 0.3em;
line-height: 1.2;
color: var(--wikt-palette-blue-9, #3366cc);
border: 0;
padding: 0;
}
/* Sized close to the heading so the card reads consistently instead of
the text below it looking like fine print. */
.wiktku-start-step-question {
font-size: 1em;
font-weight: 400;
color: var(--wikt-palette-grey-9, #555);
margin: 0 0 0.5em;
line-height: 1.45;
}
.wiktku-start-step label {
padding-right: 1.2em;
font-size: 1em;
}
/* Muted hint text next to a field, styled to read as a hint rather than
another question competing with .wiktku-start-step-question. */
.wiktku-step-hint {
font-size: 0.88em;
font-style: italic;
color: var(--wikt-palette-grey-8, #72777d);
margin: 0 0 0.5em;
line-height: 1.4;
}
/* Same specificity as the rule just above, so it must come after to
actually win: the name label isn't a field label needing right
padding or the form's base label size, it's a heading. */
#wiktku-start-form .wiktku-start-step-name label {
padding-right: 0;
font-size: inherit !important;
color: inherit;
}
input[type="text"].wiktku-start-step-field {
width: 100%;
padding: 5px 8px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 4px;
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
input[type="text"].wiktku-start-step-field:focus {
border-color: var(--wikt-palette-blue-9, #3366cc);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9, #3366cc);
outline: 0;
}
input[type="text"].wiktku-start-step-field.wiktku-start-narrow {
width: 20em;
}
/* Only used by selects that opt out via .wiktku-plain-select; other
selects get their width from the searchable picker instead (see
input.wiktku-ac-input). */
select.wiktku-start-narrow,
input.wiktku-dfn-tpl-title {
width: 20em;
max-width: 100%;
box-sizing: border-box;
}
#wiktku-start-spinner {
float: right;
margin-left: 1em;
}
#wiktku-start-footer {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5em;
padding: 0.6em 0.2em;
position: sticky;
bottom: 0;
background: var(--wikt-palette-white);
border-top: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
z-index: 5;
}
/* Quiet, non-competing text — sits next to the cache buttons so the
person can tell at a glance whether their latest edits are backed
up, without it fighting the buttons for attention. */
#wiktku-last-saved {
font-size: 0.85em;
color: var(--wikt-palette-grey-9, #767676);
white-space: nowrap;
}
/* ── Tercîh (settings) icon button — sits in the footer next to the
cache buttons. Uses the Codex "settings" (color-subtle) icon from
Commons directly rather than an OOUI icon-pack module, so it renders
the exact requested glyph without pulling in an extra dependency. */
.wiktku-prefs-btn {
width: 2.2em;
height: 2.2em;
flex: 0 0 auto;
padding: 0;
border: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
border-radius: 2px;
background-color: var(--wikt-palette-white);
background-image: url(//upload.wikimedia.org/wikipedia/commons/e/e0/Codex_icon_settings_color-subtle.svg);
background-repeat: no-repeat;
background-position: center;
background-size: 1.2em 1.2em;
cursor: pointer;
}
.wiktku-prefs-btn:hover {
background-color: var(--wikt-palette-grey-0, #f8f9fa);
}
.wiktku-prefs-btn:active {
background-color: var(--wikt-palette-grey-1, #eaecf0);
}
/* Tercîh dialog body — OOUI's bare ProcessDialog body has no inset of
its own, so text/fields otherwise sit flush against the window edges. */
.wiktku-prefs-dialog-body {
padding: 1em 1.2em;
}
/* ── "Bişîne" click feedback ── */
@keyframes wiktku-btn-flash-pulse {
0% { box-shadow: 0 0 0 0 rgba(51, 102, 204, 0.55); }
70% { box-shadow: 0 0 0 9px rgba(51, 102, 204, 0); }
100% { box-shadow: 0 0 0 0 rgba(51, 102, 204, 0); }
}
#wiktku-unhide-editform.wiktku-btn-flash {
animation: wiktku-btn-flash-pulse 0.5s ease-out;
}
/* Persistent state while the wikitext is being built/submitted, set
alongside OOUI's own setDisabled(true) so it reads clearly even if
the skin's default disabled style is subtle. */
#wiktku-unhide-editform.wiktku-btn-sending .oo-ui-buttonElement-button {
background-color: var(--wikt-palette-grey-9, #767676) !important;
border-color: var(--wikt-palette-grey-9, #767676) !important;
cursor: progress;
opacity: 1 !important;
}
/* Looks like OOUI's disabled state but isn't one — the button stays
clickable so its handler can still run and explain why (see the
wiktkuNotify call next to wiktkuContentGateOk in start.js). */
#wiktku-unhide-editform.wiktku-btn-soft-disabled .oo-ui-buttonElement-button {
opacity: 0.5;
cursor: not-allowed;
}
/* ── "Beşên din" picker ── */
.wiktku-besendin-picker {
margin: 0.6em 0 1em;
}
.wiktku-besendin-picker .wiktku-besendin-select {
width: 100%;
max-width: 24em;
}
#wiktku-start-form textarea.wiktku-start-step-field,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield,
#wiktku-start-form textarea.wiktku-word-input {
resize: vertical !important;
overflow: auto !important;
box-sizing: border-box !important;
vertical-align: top;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px;
transition: border-color 0.1s ease, box-shadow 0.1s ease;
}
/* Modern focus state to match the searchable-picker inputs. */
#wiktku-start-form textarea.wiktku-start-step-field:focus,
#wiktku-start-form textarea.wiktku-start-narrow:focus,
#wiktku-start-form textarea.wiktku-dfn-subfield:focus,
#wiktku-start-form textarea.wiktku-word-input:focus {
border-color: var(--wikt-palette-blue-9, #3366cc);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9, #3366cc);
outline: 0;
}
/* Standard: every textarea in the form is full width by default */
#wiktku-start-form textarea:not(.wiktku-start-narrow) {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 20em;
max-width: 100%;
min-width: 8em;
height: calc(1.7em + 10px);
min-height: calc(1.7em + 10px);
padding-top: 4px !important;
padding-bottom: 4px !important;
line-height: 1.35 !important;
resize: none !important;
}
/* Exception: "Sernav" / "Sernavê orjînal" citation-source fields (kitêb,
kovar, malper, …) — long enough to be worth 2 rows and manual resizing. */
#wiktku-start-form textarea.wiktku-dfn-quote-src-input.wiktku-dfn-quote-src-input--title {
height: calc(2 * 1.35em + 10px);
min-height: calc(2 * 1.35em + 10px);
}
/* ── Manual drag-to-resize handle ── */
#wiktku-start-form textarea:not(.wiktku-start-narrow) {
resize: none !important;
}
/* Inline modifier button (⊕) */
/* Each word is always a plain box (never collapses into a pill shape); its
attached-modifier chips, if any, sit on their own line right above the
box, and the box + its ⊕/× buttons sit on their own line below that. */
.wiktku-word-li {
display: block;
}
/* ── ⊕-modifier word lists (Hevmane/Dijmane/Formên alternatîv/Termên
koordîne/Jornav/Jêrnav — both the top-level steps and the per-sense
sub-groups): every word is its own always-visible, always-editable box,
one per line — no packing/wrapping of multiple words onto one row. ── */
#wiktku-start-form .wiktku-dfn-sublist > li.wiktku-word-li {
margin-bottom: 6px;
}
/* Modifier chip row: sits above the word's box (DOM order in start.js
already places it first; this just spaces it out from the box below). */
#wiktku-start-form .wiktku-word-mods {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 3px;
}
#wiktku-start-form .wiktku-word-mod-chip {
display: inline-flex;
align-items: center;
gap: 4px;
background: var(--wikt-card);
color: var(--wikt-ink-2);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
font-size: 11.5px;
padding: 2px 4px 2px 8px;
cursor: pointer;
}
#wiktku-start-form .wiktku-word-mod-chip-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 12px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
#wiktku-start-form .wiktku-word-mod-chip-remove:hover {
color: var(--wikt-danger);
}
/* Word box + its ⊕ (add modifier) / × (remove word) buttons, side by side. */
#wiktku-start-form .wiktku-word-box-row {
display: flex;
align-items: center;
gap: 4px;
}
#wiktku-start-form .wiktku-word-box-row .wiktku-word-input {
flex: 1 1 auto;
}
#wiktku-start-form .wiktku-word-remove {
flex: 0 0 auto;
width: 1.6em;
height: 1.6em;
padding: 0;
line-height: 1;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
color: var(--wikt-muted);
font-size: 1em;
cursor: pointer;
}
#wiktku-start-form .wiktku-word-remove:hover {
border-color: var(--wikt-danger);
color: var(--wikt-danger);
}
.wiktku-mod-btn {
flex: 0 0 auto;
width: 1.6em;
height: 1.6em;
padding: 0;
line-height: 1;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
color: var(--wikt-palette-blue-9, #3366cc);
font-size: 1em;
cursor: pointer;
}
.wiktku-mod-btn:hover {
border-color: var(--wikt-palette-blue-9, #3366cc);
background: var(--wikt-palette-paleblue, #eaf3ff);
}
.wiktku-mod-picker {
display: flex;
flex-wrap: wrap;
gap: 0.25em;
width: 100%;
margin-top: 0.15em;
padding: 0.35em;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px;
background: var(--wikt-palette-white);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
}
.wiktku-mod-pill {
padding: 2px 8px;
border: 1px solid var(--wikt-palette-grey-3, #e0e0e0);
border-radius: 12px;
background: var(--wikt-palette-paleblue, #eaf3ff);
font-size: 0.85em;
white-space: nowrap;
cursor: pointer;
}
.wiktku-mod-pill:hover {
border-color: var(--wikt-palette-blue-9, #3366cc);
background: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-white);
}
.wiktku-mod-pill:disabled,
.wiktku-mod-pill.wiktku-mod-pill-used {
border-color: var(--wikt-palette-grey-3, #e0e0e0);
background: var(--wikt-palette-lightgrey);
color: var(--wikt-palette-grey-3, #999);
cursor: not-allowed;
opacity: 0.6;
}
.wiktku-mod-pill:disabled:hover,
.wiktku-mod-pill.wiktku-mod-pill-used:hover {
border-color: var(--wikt-palette-grey-3, #e0e0e0);
background: var(--wikt-palette-lightgrey);
color: var(--wikt-palette-grey-3, #999);
}
.wiktku-mod-val-wrap {
display: flex;
align-items: center;
gap: 0.25em;
width: 100%;
margin-top: 0.15em;
}
.wiktku-mod-val-input {
flex: 0 1 14em;
max-width: 14em;
padding: 3px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
}
.wiktku-mod-confirm,
.wiktku-mod-cancel {
flex: 0 0 auto;
width: 1.6em;
height: 1.6em;
padding: 0;
line-height: 1;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
cursor: pointer;
}
.wiktku-mod-confirm {
color: #178a17;
border-color: #178a17;
}
.wiktku-mod-cancel {
color: var(--wikt-palette-red-9, #d33);
border-color: var(--wikt-palette-red-9, #d33);
}
.wiktku-resize-wrap {
position: relative;
display: block;
}
/* Separate from .wiktku-resize-wrap (used only for textareas flagged with
a linebreak); plain position:relative anchor for the backdrop, no size of its own. */
.wiktku-linebreak-wrap {
position: relative;
display: block;
}
.wiktku-resize-grip {
position: absolute;
right: 2px;
bottom: 2px;
width: 14px;
height: 14px;
cursor: ns-resize;
touch-action: none;
opacity: 0.5;
z-index: 2;
background-image: linear-gradient(135deg, transparent 0 50%, var(--wikt-palette-grey-9, #767676) 50% 100%);
background-repeat: no-repeat;
}
.wiktku-resize-grip:hover,
.wiktku-resize-grip:active {
opacity: 0.9;
}
/* ===================================================================
Searchable dropdown (wrapNativeSelect in start.js)
.wiktku-native-select-wrap > input.wiktku-ac-input (search box)
> ul.wiktku-ac-results (result list)
> select (hidden, holds the value)
=================================================================== */
.wiktku-native-select-wrap {
display: inline-block;
position: relative;
vertical-align: top;
}
/* Matches the width the native select would have had (see
select.wiktku-start-narrow). */
.wiktku-native-select-wrap input[type="search"],
input.wiktku-ac-input {
display: block;
width: 20em;
max-width: 100%;
box-sizing: border-box;
padding: 3px 6px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 2px;
background: var(--wikt-palette-white);
color: inherit;
font-size: inherit;
font-family: inherit;
transition: border-color 0.1s ease, box-shadow 0.1s ease;
}
input.wiktku-ac-input:focus {
border-color: var(--wikt-palette-blue-9);
box-shadow: inset 0 0 0 1px var(--wikt-palette-blue-9);
outline: 0;
}
input.wiktku-ac-input::placeholder {
color: var(--wikt-palette-grey-9);
opacity: 1;
}
/* Hide Safari/Chrome's built-in search decorations. */
input.wiktku-ac-input::-webkit-search-decoration,
input.wiktku-ac-input::-webkit-search-cancel-button,
input.wiktku-ac-input::-webkit-search-results-button {
-webkit-appearance: none;
appearance: none;
}
/* ── Result list ── */
/* Stays below MediaWiki's own .mw-notification-area (z-index: 10000) so the dropdown never covers a toast notification. */
ul.wiktku-ac-results {
position: absolute;
z-index: 500;
top: 100%;
bottom: auto;
left: 0;
margin: 0;
padding: 0;
list-style: none;
width: 20em;
max-width: 90vw;
max-height: 13em;
overflow-y: auto;
overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
background: var(--wikt-palette-white);
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 3px;
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.15);
}
ul.wiktku-ac-results li {
margin: 0;
padding: 4px 8px;
cursor: pointer;
font-size: 0.92em;
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* .wiktku-ac-active is the keyboard cursor; hover mirrors it; :active
covers the touch press state. */
ul.wiktku-ac-results li.wiktku-ac-active,
ul.wiktku-ac-results li:hover {
background: var(--wikt-palette-lightblue);
}
ul.wiktku-ac-results li:active {
background: var(--wikt-palette-blue-9);
color: var(--wikt-palette-white);
}
/* Touch devices: bigger tap targets, no sticky hover. */
@media (pointer: coarse) {
ul.wiktku-ac-results li {
padding: 6px 10px;
min-height: 2.2em;
font-size: 1em;
}
ul.wiktku-ac-results li:hover {
background: transparent;
}
ul.wiktku-ac-results li.wiktku-ac-active {
background: var(--wikt-palette-lightblue);
}
input.wiktku-ac-input {
/* >=16px stops iOS Safari zooming the page when the box is focused. */
font-size: 16px;
min-height: 2.2em;
}
}
/* Inline preview + confirm panel shown before inserting an auto-content
pronunciation template, part of the form layout rather than a browser
confirm()/prompt() dialog. */
.wiktku-pron-preview {
margin: 6px 0;
padding: 8px 10px;
border: 1px solid var(--wikt-palette-blue-9, #3366cc);
border-radius: 6px;
background: var(--wikt-palette-paleblue, #eaf3ff);
}
.wiktku-pron-preview-label {
font-size: 0.88em;
font-weight: 600;
color: var(--wikt-palette-grey-9, #555);
margin-bottom: 3px;
}
.wiktku-pron-preview-body {
font-family: monospace;
font-size: 1em;
white-space: pre-wrap;
word-break: break-word;
margin-bottom: 6px;
}
.wiktku-pron-preview-actions {
display: flex;
gap: 6px;
}
.wiktku-pron-preview-confirm,
.wiktku-pron-preview-cancel {
font-size: 0.88em;
padding: 3px 10px;
border-radius: 4px;
cursor: pointer;
border: 1px solid var(--wikt-palette-lightgrey, #a2a9b1);
background: var(--wikt-palette-white, #fff);
}
.wiktku-pron-preview-confirm {
border-color: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-blue-9, #3366cc);
font-weight: 600;
}
.wiktku-pron-preview-confirm:disabled {
opacity: 0.5;
cursor: wait;
}
.wiktku-pron-preview-cancel:hover {
background: var(--wikt-palette-grey-0, #f8f9fa);
}
/* Template pickers fill their container since the search box, not the
<select>, is what the user sees. */
.wiktku-pron-wrap .wiktku-native-select-wrap,
.wiktku-etym-wrap .wiktku-native-select-wrap,
.wiktku-pron-wrap .wiktku-native-select-wrap input.wiktku-ac-input,
.wiktku-etym-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
width: 100%;
}
.wiktku-pron-wrap .wiktku-native-select-wrap input.wiktku-ac-input,
.wiktku-etym-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
font-size: 0.88em;
}
.wiktku-cavkani-wrap .wiktku-native-select-wrap,
.wiktku-cavkani-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
width: 100%;
}
/* Template selector inside a sense block. */
.wiktku-dfn-tpl-row .wiktku-native-select-wrap input.wiktku-ac-input {
font-size: 0.88em;
}
/* Language/dialect rows: picker must line up with the word field beside it. */
.wiktku-lang-row .wiktku-native-select-wrap {
flex: 0 0 auto;
}
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form textarea.wiktku-dfn-nimune-text,
#wiktku-start-form .wiktku-dfn-jeder-group textarea.wiktku-dfn-nimune-quote,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t,
#wiktku-start-form textarea.wiktku-dfn-quote-src-manual,
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form .wiktku-tewandin-wrap textarea.wiktku-tewandin-text,
#wiktku-start-form textarea.wiktku-cavkani-text,
#wiktku-start-form textarea.wiktku-dfn-tercime,
#wiktku-start-form textarea.wiktku-dfn-izah {
--wiktku-ta-rows: 2;
height: calc(var(--wiktku-ta-rows) * 1.35em + 6px);
min-height: calc(var(--wiktku-ta-rows) * 1.35em + 6px);
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text,
#wiktku-start-form textarea.wiktku-dfn-izah {
--wiktku-ta-rows: 3;
}
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
}
/* Mane, Nimûne + Jêgirtin (citation) boxes, and their tr/t sub-fields: at
least 4 rows so there's room to actually write a definition/example/quote.
Manual resize (see util.js) still lets the person grow or shrink any of
these afterward — this only sets where they start. */
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form textarea.wiktku-dfn-nimune-text,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
#wiktku-start-form .wiktku-dfn-jeder-group textarea.wiktku-dfn-nimune-quote,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t,
#wiktku-start-form textarea.wiktku-dfn-quote-src-manual {
--wiktku-ta-rows: 4;
}
/* Nudges the border when the box contains a typed/pasted linebreak, so
the user notices before it's silently turned into <br> on output; the
break itself is marked via a mirrored backdrop layer (see below). */
#wiktku-start-form textarea.wiktku-has-linebreak {
border-color: var(--wikt-palette-yellow6, #ac6600) !important;
}
/* Sits behind the textarea showing the same text, with a highlighted
marker at each line followed by a break; font/padding/line-height are
copied from the textarea via computed style, not shared CSS classes. */
.wiktku-linebreak-backdrop {
position: absolute;
inset: 0;
z-index: 0;
margin: 0;
overflow: hidden;
white-space: pre-wrap;
word-wrap: break-word;
color: transparent;
background: transparent !important;
border-color: transparent !important;
box-shadow: none !important;
resize: none !important;
pointer-events: none;
}
/* Raise the real textarea above its backdrop so typing/selection/caret
behave completely normally; the backdrop's mark shows through only
where the real textarea's own background is transparent. */
#wiktku-start-form textarea.wiktku-has-linebreak-source {
position: relative;
z-index: 1;
background-color: transparent;
}
.wiktku-linebreak-mark {
background-color: var(--wikt-palette-yellow6, #ac6600);
color: var(--wikt-palette-white, #fff);
border-radius: 2px;
}
/* ── Tercime + Îzah boxes ── */
.wiktku-dfn-nonku-wrap {
display: flex;
flex-direction: column;
gap: 3px;
}
.wiktku-dfn-nimune-sub,
.wiktku-dfn-quote-sub {
margin-top: 3px;
margin-left: 1em;
padding-left: 0.6em;
border-left: 2px solid var(--wikt-palette-lightgrey);
display: flex;
flex-direction: column;
gap: 3px;
}
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
.wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t {
min-width: 8em;
padding-top: 2px !important;
padding-bottom: 2px !important;
line-height: 1.35 !important;
font-size: 0.88em;
}
/* Sense list (Mane step) — one sense visible at a time, switched via
.wiktku-dfn-sense-tabs. */
ol.wiktku-dfn-list {
list-style: none;
margin: 0;
padding: 0;
}
ol.wiktku-dfn-list > li.wiktku-dfn-sense {
margin-bottom: 0.9em;
}
/* Template selector row inside a sense block */
.wiktku-dfn-tpl-row {
margin-bottom: 3px;
}
/* ── Sub-field titles (Etîket, Kategorî, Hevmane, Dijmane, Nimûne,
Jêgirtin, …): each group reads as a small labelled field — the term
stands out, the hint stays quiet. ── */
.wiktku-dfn-subgroup {
margin-top: 0.5em;
padding-top: 0.4em;
border-top: 1px dashed var(--wikt-palette-grey-3, #e0e0e0);
}
.wiktku-dfn-subgroup:first-of-type {
margin-top: 0.3em;
padding-top: 0;
border-top: none;
}
.wiktku-dfn-sublabel {
display: block;
margin-bottom: 3px;
font-size: 0.88em;
}
.wiktku-dfn-sublabel-term {
font-weight: 600;
color: var(--wikt-palette-blue-9, #3366cc);
letter-spacing: 0.03em;
font-size: 0.92em;
}
.wiktku-dfn-sublabel-hint {
color: var(--wikt-palette-grey-9, #767676);
font-style: italic;
}
.wiktku-pron-wrap,
.wiktku-etym-wrap,
.wiktku-tewandin-wrap,
.wiktku-cavkani-wrap {
display: flex;
flex-direction: column;
gap: 3px;
}
/* ── POS section tabs — rounded pill style ── */
#wiktku-section-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 0.6em;
align-items: center;
}
#wiktku-section-tabs .wiktku-section-tab {
padding: 3px 12px;
border: 1px solid var(--wikt-palette-lightgrey);
border-radius: 999px;
background: var(--wikt-palette-dullcyan);
cursor: pointer;
font-size: 0.86em;
user-select: none;
transition: background 0.12s ease, border-color 0.12s ease;
}
#wiktku-section-tabs .wiktku-section-tab:hover {
border-color: var(--wikt-palette-blue-9, #3366cc);
}
#wiktku-section-tabs .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-palette-blue-9, #3366cc);
border-color: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-white);
font-weight: 600;
}
/* ── Step-group tabs (Mane / Bilêvkirin / Beşên din — see STEP_GROUPS in
start.js) ── Sits directly under the pill-style section tabs above;
styled as an underlined secondary nav so the two bars stay visually
distinct. */
#wiktku-group-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 0.8em;
border-bottom: 2px solid var(--wikt-palette-lightgrey);
}
#wiktku-group-tabs .wiktku-group-tab {
position: relative;
padding: 7px 14px 8px;
margin-bottom: -2px;
border-bottom: 3px solid transparent;
border-radius: 3px 3px 0 0;
cursor: pointer;
font-size: 0.95em;
font-weight: 600;
color: var(--wikt-palette-grey-9, #333);
background: var(--wikt-palette-grey-0, #f8f9fa);
user-select: none;
transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
#wiktku-group-tabs .wiktku-group-tab:hover {
color: var(--wikt-palette-blue-9, #3366cc);
background: var(--wikt-palette-paleblue, #eaf3ff);
}
#wiktku-group-tabs .wiktku-group-tab.wiktku-tab-active {
border-bottom-color: var(--wikt-palette-blue-9, #3366cc);
color: var(--wikt-palette-white);
background: var(--wikt-palette-blue-9, #3366cc);
font-weight: 700;
}
/* Small dot marking a tab that still has an unfilled required field
hidden inside it (see updateGroupTabIndicators in start.js). Cleared
automatically once that tab becomes the active one. */
#wiktku-group-tabs .wiktku-group-tab.wiktku-tab-missing::after {
content: "";
position: absolute;
top: 4px;
right: -1px;
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--wikt-palette-red-9, #d33);
}
.wiktku-lang-row-list {
margin: 0;
padding: 0;
list-style: none;
}
.wiktku-lang-row {
display: flex;
align-items: flex-start;
gap: 6px;
margin-bottom: 3px;
flex-wrap: wrap;
}
.wiktku-lang-row-words {
margin: 0;
padding: 0;
list-style: none;
display: flex;
flex-direction: column;
gap: 3px;
}
/* Dûnde/Werger/Zarava rows: align the picker's height with the adjacent
word textarea (see .wiktku-start-narrow above). */
.wiktku-lang-row .wiktku-native-select-wrap input.wiktku-ac-input {
height: calc(1.7em + 10px);
min-height: calc(1.7em + 10px);
padding-top: 4px;
padding-bottom: 4px;
}
/* ── Werger / Bi zaravayên din groups: one per gloss, shared spacing ── */
#wiktku-start-trans .wiktku-trans-group,
#wiktku-start-zarava .wiktku-zarava-group {
margin-bottom: 0.6em;
}
#wiktku-start-trans .wiktku-trans-group:last-child,
#wiktku-start-zarava .wiktku-zarava-group:last-child {
margin-bottom: 0;
}
#wiktku-start-trans .wiktku-trans-group-label,
#wiktku-start-zarava .wiktku-zarava-group-label {
font-weight: 600;
font-size: 0.88em;
margin-bottom: 3px;
}
.wiktku-dfn-jeder-group {
margin-top: 3px;
}
/* Numbers each citation ("Jêgirtin 1:", "Jêgirtin 2:"…) and separates entries with a rule so multiple ones don't blur together. */
.wiktku-dfn-jeder-list {
counter-reset: wiktku-jeder-num;
}
.wiktku-dfn-jeder-list > li.wiktku-dfn-jeder-li {
counter-increment: wiktku-jeder-num;
padding-top: 8px;
border-top: 1px solid var(--wikt-palette-lightgrey, #eaecf0);
}
.wiktku-dfn-jeder-list > li.wiktku-dfn-jeder-li:first-child {
padding-top: 0;
border-top: none;
}
.wiktku-dfn-jeder-list > li.wiktku-dfn-jeder-li::before {
content: "Jêgirtin " counter(wiktku-jeder-num) ":";
display: block;
margin-bottom: 3px;
font-weight: 600;
font-size: 0.82em;
color: var(--wikt-palette-blue-9, #3366cc);
letter-spacing: 0.03em;
}
.wiktku-dfn-quote-src-sel {
font-size: 0.88em;
margin-top: 3px;
display: block;
/* A native select with no width rule sizes to fit its longest <option>
(here, the longest çavkanî template name) rather than its container,
which overflows the card on a phone — cap it like every other field. */
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* Wide/narrow/medium layout, same idea as MediaWiki:Gadget-wikt.add-
examples.js: rows wrap freely, each sized by its own width tier
(.wiktku-dfn-quote-src-row--wide/narrow/medium) instead of always
stacking one field per line. */
.wiktku-dfn-quote-src-fields {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
gap: 6px 10px;
margin-top: 3px;
}
/* Fills the flex row so the manual-source textarea's resize wrapper doesn't shrink to the browser's default width. */
.wiktku-dfn-quote-src-fields > .wiktku-resize-wrap {
flex: 1 1 100%;
width: 100%;
}
/* Stacked layout: textarea gets its own full-width row, button + status
sit below it on their own row. */
.wiktku-dfn-quote-citoid {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 0.4em;
margin-top: 3px;
margin-bottom: 3px;
}
.wiktku-dfn-quote-citoid-input {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* Column flex + align-items:stretch (above) makes the textarea's
.wiktku-resize-wrap reach full width automatically; button and status
text opt back out via align-self so they keep their natural size. */
.wiktku-dfn-quote-citoid-btn,
.wiktku-dfn-quote-citoid-status {
align-self: flex-start;
}
.wiktku-dfn-quote-src-cat-status {
display: block;
font-size: 0.82em;
color: var(--wikt-palette-lightgrey, #767676);
margin-top: 2px;
}
.wiktku-dfn-quote-src-row {
display: flex;
flex-direction: column;
gap: 2px;
flex: 1 1 12em;
}
.wiktku-dfn-quote-src-row--wide {
flex-basis: 100%;
}
.wiktku-dfn-quote-src-row--narrow {
flex: 0 1 7em;
}
.wiktku-dfn-quote-src-row--medium {
flex: 1 1 12em;
}
.wiktku-dfn-quote-src-row label {
font-size: 0.82em;
white-space: nowrap;
}
.wiktku-dfn-quote-src-input {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
@media (max-width: 720px) {
.wiktku-lang-row {
flex-direction: column;
}
/* Re-asserts against the skin's own mobile-breakpoint heading styles,
which load after ours in the cascade. */
#wiktku-start-form .wiktku-start-step-name {
font-size: 1.05em !important;
margin: 0 0 0.2em !important;
line-height: 1.2 !important;
border: 0 !important;
padding: 0 !important;
}
/* Single column: search box, list, and narrow textareas (Etîket,
Hevmane, Dûnde/Jê/Têkildar/Binêre herwiha word boxes, citation
source fields, …) all use the full width. */
.wiktku-native-select-wrap,
input.wiktku-ac-input,
ul.wiktku-ac-results,
#wiktku-start-form textarea.wiktku-start-narrow,
#wiktku-start-form textarea.wiktku-dfn-subfield.wiktku-start-narrow {
width: 100%;
max-width: 100%;
}
.wiktku-mod-val-input {
flex: 1 1 auto;
max-width: 100%;
}
#wiktku-start-form .wiktku-pron-wrap textarea.wiktku-pron-text,
#wiktku-start-form .wiktku-etym-wrap textarea.wiktku-etym-text {
--wiktku-ta-rows: 3;
font-size: 1em;
}
#wiktku-start-form .wiktku-tewandin-wrap textarea.wiktku-tewandin-text,
#wiktku-start-form textarea.wiktku-cavkani-text {
--wiktku-ta-rows: 4;
font-size: 1em;
}
/* Mane, Nimûne + Jêgirtin and their tr/t sub-fields: keep at least 4 rows
on mobile too, rather than shrinking further than the desktop size. */
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form textarea.wiktku-dfn-nimune-text,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-tr,
#wiktku-start-form .wiktku-dfn-nimune-sub textarea.wiktku-dfn-nimune-t,
#wiktku-start-form .wiktku-dfn-jeder-group textarea.wiktku-dfn-nimune-quote,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-tr,
#wiktku-start-form .wiktku-dfn-quote-sub textarea.wiktku-dfn-nimune-quote-t {
--wiktku-ta-rows: 4;
font-size: 1em;
}
/* Citoid URL/DOI/ISBN box: taller tap target (~2 lines) on mobile,
and now that this field is a <textarea> (see start.js) its
placeholder can actually wrap into that space — a plain <input>'s
placeholder can never wrap, no matter its height. */
.wiktku-dfn-quote-citoid-input {
min-height: calc(2 * 1.35em + 10px);
font-size: 1em;
}
.wiktku-dfn-nimune-sub,
.wiktku-dfn-quote-sub {
margin-left: 0.5em;
padding-left: 0.4em;
}
.wiktku-pron-wrap .wiktku-native-select-wrap input.wiktku-ac-input,
.wiktku-etym-wrap .wiktku-native-select-wrap input.wiktku-ac-input {
font-size: 1em;
min-height: 2.2em;
}
/* Every field stacks full-width on mobile, regardless of its wide/
narrow/medium tier — there's no room to wrap several per line. */
#wiktku-start-form .wiktku-dfn-quote-src-row {
flex-basis: 100%;
}
.wiktku-start-step {
padding: 0.5em 0.6em;
}
}
/* ===================================================================
Night mode
Palette.css already flips --wikt-palette-* vars for night mode; this
hook only covers real colours (panel border) that aren't a palette var,
since a drop shadow is invisible against an equally dark page.
=================================================================== */
@media screen {
html.skin-theme-clientpref-night .wiktku-ac-results {
/* Border stands in for the shadow, invisible on a dark page. */
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
html.skin-theme-clientpref-night .wiktku-start-step,
html.skin-theme-clientpref-night .wiktku-start-step:hover,
html.skin-theme-clientpref-night .wiktku-start-step:focus-within {
box-shadow: none;
}
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os .wiktku-ac-results {
border-color: var(--wikt-palette-grey-5);
box-shadow: 0 3px 10px rgba(0, 0, 0, 0.6);
}
html.skin-theme-clientpref-os .wiktku-start-step,
html.skin-theme-clientpref-os .wiktku-start-step:hover,
html.skin-theme-clientpref-os .wiktku-start-step:focus-within {
box-shadow: none;
}
}
/* "Zimanek lê zêde bike" portlet icon (Minerva/mobile) — Feather-core-edit.svg
via CSS mask, matching Minerva's own icon pattern. */
.minerva-icon-portletlink-ca-wiktku-addlang {
-webkit-mask-image: url("https://upload.wikimedia.org/wikipedia/commons/d/d7/Feather-core-edit.svg");
mask-image: url("https://upload.wikimedia.org/wikipedia/commons/d/d7/Feather-core-edit.svg");
-webkit-mask-repeat: no-repeat;
mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-position: center;
-webkit-mask-size: 20px;
mask-size: 20px;
background-color: currentColor;
background-image: none;
}
/* ── posparams step (live wikitext + helper widgets) ── */
.wiktku-posparams-wrap {
display: flex;
flex-direction: column;
gap: 6px;
}
.wiktku-posparams-helpers {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-posparams-row {
display: flex;
align-items: center;
gap: 6px;
flex-wrap: wrap;
}
.wiktku-posparams-row label {
font-size: 1em;
min-width: 10em;
padding-right: 0;
}
.wiktku-posparams-subheading {
font-weight: 600;
font-size: 0.88em;
margin-top: 6px;
}
.wiktku-posparams-pronoun-hint {
font-size: 0.9em;
color: var(--wikt-ink-2);
font-style: italic;
}
/* Keeps a pronoun hint and its box on one line, immediately next to each
other, even when the row around them stacks label-over-field (see the
desktop grid layout for .wiktku-posparams-helpers below). */
.wiktku-posparams-field-line {
display: flex;
align-items: center;
gap: 6px;
}
.wiktku-posparams-text {
width: 100%;
box-sizing: border-box;
font-family: monospace;
font-size: 0.9em;
}
.wiktku-posparams-opt-toggle {
font-size: 0.85em;
margin-top: 2px;
display: inline-block;
}
.wiktku-posparams-opt-panel {
display: flex;
flex-direction: column;
gap: 2px;
padding: 4px 0 2px 1em;
}
.wiktku-posparams-opt-row {
display: flex;
align-items: center;
gap: 4px;
font-size: 0.85em;
}
/* ── resim step (one or more images) ── */
.wiktku-resim-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.wiktku-resim-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.wiktku-resim-item .wiktku-posparams-row {
flex-direction: column;
align-items: flex-start;
gap: 2px;
}
.wiktku-resim-item .wiktku-posparams-row label {
min-width: 0;
font-size: 0.9em;
}
/* ── dfn: "hide/show all subsections" link ──
Toggles a class on the whole dfn stepDiv so one click hides the
etîket/kategorî/hevmane/dijmane/nimûne/jêgirtin/etc. subgroups
across every sense block at once. */
.wiktku-dfn-subs-toggle {
font-size: 0.85em;
font-weight: 500;
display: inline-block;
margin: 2px 0 8px;
color: var(--wikt-palette-blue-9, #3366cc);
text-decoration: none;
}
.wiktku-dfn-subs-toggle:hover {
text-decoration: underline;
}
/* Only the ADVANCED subgroups collapse: Nimûne, Jêgirtin, Hevmane and
Dijmane stay open because they are what people come here to fill in. */
#wiktku-start-form li.wiktku-sense-adv-hidden .wiktku-dfn-subgroup-adv {
display: none !important;
}
/* ── "Malûmatên bikaranînê" accordion — fieldset-style: a legend that
floats on the box's top border (negative translateY + card-color
background "cutting through" the border line), collapsed by default.
See buildSenseBlock in start.js. ── */
#wiktku-start-form .wiktku-sense-adv-box {
margin-top: 14px;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-card, 12px);
padding: 0 16px 16px;
}
#wiktku-start-form .wiktku-sense-adv-legend {
display: inline-flex;
align-items: center;
gap: 8px;
transform: translateY(-10px);
background: var(--wikt-card);
padding: 0 9px;
margin-left: 4px;
}
#wiktku-start-form .wiktku-sense-adv-diamond {
font-size: 12px;
color: var(--wikt-muted);
}
#wiktku-start-form .wiktku-sense-adv-label {
font-size: 12.5px;
font-weight: 500;
color: var(--wikt-ink-2);
}
#wiktku-start-form .wiktku-sense-adv-chevron {
width: 20px;
height: 20px;
border-radius: 6px;
border: 1px solid var(--wikt-field-edge);
color: var(--wikt-ink-2);
font-size: 10px;
display: grid;
place-items: center;
background: var(--wikt-card-sunken);
cursor: pointer;
padding: 0;
}
#wiktku-start-form .wiktku-sense-adv-chevron:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
}
/* Collapsed-only summary: a dim one-line status, so glancing at a closed
accordion still says whether there's anything back there. Hidden while
expanded — the real fields (inside .wiktku-dfn-subgroup-adv, toggled by
li.wiktku-sense-adv-hidden above) take its place. */
#wiktku-start-form .wiktku-sense-adv-summary {
display: none;
align-items: center;
gap: 9px;
}
#wiktku-start-form li.wiktku-sense-adv-hidden .wiktku-sense-adv-summary {
display: flex;
}
#wiktku-start-form .wiktku-sense-adv-summary-dots {
letter-spacing: .18em;
color: var(--wikt-muted);
}
#wiktku-start-form .wiktku-sense-adv-summary-text {
font-size: 13.5px;
color: var(--wikt-muted);
}
/* ===================================================================
Desktop text bump
Modifier pills/buttons and posparams helper text read fine at their
small mobile sizes but look too small on desktop, so bump them up
past the 720px breakpoint only.
=================================================================== */
@media (min-width: 721px) {
.wiktku-mod-btn,
.wiktku-mod-confirm,
.wiktku-mod-cancel {
width: 1.8em;
height: 1.8em;
font-size: 1.1em;
}
.wiktku-mod-pill {
font-size: 0.95em;
padding: 3px 10px;
}
.wiktku-mod-val-input {
font-size: 0.95em;
}
.wiktku-posparams-row label {
font-size: 1em;
}
.wiktku-posparams-text {
font-size: 1em;
}
.wiktku-posparams-opt-toggle {
font-size: 0.95em;
}
.wiktku-posparams-opt-row {
font-size: 0.95em;
}
.wiktku-dfn-subs-toggle {
font-size: 0.95em;
}
/* Multi-column card layout for posparams helper fields (transliteration,
gendered forms, comparative/superlative, hanja, verb-tense roots, …),
desktop-only. Rows are shown/hidden via jQuery inline display; the
grid only rearranges visible rows, not which ones are visible. */
.wiktku-posparams-helpers {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
gap: 10px 16px;
align-items: start;
}
.wiktku-posparams-helpers > .wiktku-posparams-row {
flex-direction: column;
align-items: stretch;
flex-wrap: nowrap;
gap: 3px;
}
.wiktku-posparams-helpers > .wiktku-posparams-row label {
min-width: 0;
}
.wiktku-posparams-helpers > .wiktku-posparams-row .wiktku-posparams-field,
.wiktku-posparams-helpers > .wiktku-posparams-row select.wiktku-posparams-cinsiyet-sel,
.wiktku-posparams-helpers > .wiktku-posparams-row select.wiktku-posparams-lektur-sel {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* The "Jimar" row lives here too — just another .wiktku-posparams-row,
no extra CSS needed. */
/* These two aren't per-field rows — span the full card width so the
collapsed toggle and its checkbox list don't get squeezed into one
narrow column. */
.wiktku-posparams-helpers > .wiktku-posparams-opt-toggle,
.wiktku-posparams-helpers > .wiktku-posparams-opt-panel,
.wiktku-posparams-helpers > .wiktku-posparams-subheading {
grid-column: 1 / -1;
}
}
/* ===================================================================
SÊWIRANA NÛ — design system layer
===================================================================
Purely the visual skin (colour, type, weight, radius, spacing), layered
after the layout rules above so it wins the cascade.
No @import of Spectral / IBM Plex from Google Fonts: Wikimedia's CSP
blocks third-party font hosts, so the stacks below name the spec's
faces first (used if the wiki installs them locally) and fall back to
faces with the same feel.
Accent colour is reserved for actionable elements (focus, links, the
active tab, the primary button, chips); section headings are near-black.
=================================================================== */
#wiktku-start-form {
/* Surfaces */
--wikt-page: #f4efe5;
--wikt-card: #fffdf8;
--wikt-card-sunken: #fbf8f1;
--wikt-rule: #ded6c8; /* structural hairline */
--wikt-rule-soft: #efe8db; /* inside a card */
--wikt-field-edge: #cfc7b8;
--wikt-field-bg: #fff;
--wikt-code-bg: #f7f4ec;
--wikt-card-edge: #e6dfd2;
/* Ink */
--wikt-ink: #1c1a17;
--wikt-ink-2: #3d3830;
--wikt-ink-3: #57514a;
--wikt-muted: #8a8378;
--wikt-placeholder: #b3ab9e;
/* Accent + status */
--wikt-accent: #2a5db0;
--wikt-accent-deep: #22406f;
--wikt-accent-tint: #eef2fa;
--wikt-accent-edge: #d5def0;
--wikt-danger: #9c3a28;
--wikt-on-accent: #fff; /* text on a filled accent button */
--wikt-link-under: rgba(42, 93, 176, 0.35);
/* Shape */
--wikt-r-field: 10px;
--wikt-r-card: 14px;
--wikt-r-pill: 999px;
--wikt-tap: 44px; /* minimum field height */
--wikt-serif: Spectral, "Iowan Old Style", Georgia, "Times New Roman", serif;
--wikt-sans: "IBM Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
--wikt-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-family: var(--wikt-sans);
font-size: 15px;
line-height: 1.55;
color: var(--wikt-ink);
background: var(--wikt-page);
padding: 24px;
border-radius: var(--wikt-r-card);
}
/* ── Cards ───────────────────────────────────────────────────────────
Flat cream panel held by a single hairline border, no shadow. */
#wiktku-start-form .wiktku-start-step {
background: var(--wikt-card);
border: 1px solid var(--wikt-card-edge);
border-left: 1px solid var(--wikt-card-edge);
border-radius: var(--wikt-r-card);
box-shadow: none;
padding: 24px 24px 20px;
margin-bottom: 16px;
transition: border-color 0.12s ease;
}
#wiktku-start-form .wiktku-start-step:hover,
#wiktku-start-form .wiktku-start-step:focus-within {
box-shadow: none;
border-color: var(--wikt-rule);
}
/* The fused pos + posparams pair keeps reading as one card. margin-bottom
has to be reset here too — .wiktku-start-step's own 16px otherwise wins
(higher specificity than the older #wiktku-start-pos-only rule this was
meant to override), leaving a visible gap between the two "halves". */
#wiktku-start-form #wiktku-start-pos {
border-radius: var(--wikt-r-card) var(--wikt-r-card) 0 0;
border-bottom: none;
padding-bottom: 8px;
margin-bottom: 0;
}
#wiktku-start-form #wiktku-start-posparams {
border-radius: 0 0 var(--wikt-r-card) var(--wikt-r-card);
border-top: none;
padding-top: 4px;
}
/* ── Type ────────────────────────────────────────────────────────────
Step headings: serif, regular weight, near-black. */
#wiktku-start-form .wiktku-start-step-name {
font-family: var(--wikt-serif);
font-size: 24px !important;
font-weight: 400;
letter-spacing: -0.01em;
line-height: 1.15;
color: var(--wikt-ink);
margin: 0 0 12px;
}
/* display:block matters: this is a <label>, so it was sharing a line with
the control it introduces wherever start.js didn't emit a <br>. */
#wiktku-start-form .wiktku-start-step-question {
display: block;
font-size: 15px;
font-weight: 400;
color: var(--wikt-ink-3);
margin: 0 0 12px;
line-height: 1.6;
max-width: 62ch;
}
/* Collapses the label entirely when empty (posparams' "Cins/zayend" title
can be blank) instead of leaving its margin behind as a gap; a no-op
for every other step, whose question text is never empty. */
#wiktku-start-form .wiktku-start-step-question:empty {
display: none;
margin: 0;
}
/* Same reason — a field label belongs above its field, not beside it. */
#wiktku-start-form .wiktku-posparams-row label,
#wiktku-start-form .wiktku-dfn-sublabel {
display: block;
margin-bottom: 6px;
padding-right: 0;
}
/* Field labels: small, medium weight, full-strength ink — they label a
control, so they should not read as body copy. */
#wiktku-start-form .wiktku-start-step label,
#wiktku-start-form .wiktku-posparams-row label,
#wiktku-start-form .wiktku-dfn-sublabel {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink);
letter-spacing: 0.01em;
}
/* The parenthetical/example half of a label is supporting text. */
#wiktku-start-form .wiktku-dfn-sublabel-hint,
#wiktku-start-form .wiktku-step-hint {
font-weight: 400;
font-style: italic;
color: var(--wikt-muted);
font-size: 12.5px;
}
/* ── Fields ──────────────────────────────────────────────────────────
One shape for every control: 1px edge, 10px radius, 44px tall. */
#wiktku-start-form input[type="text"],
#wiktku-start-form input[type="search"],
#wiktku-start-form input.wiktku-ac-input,
#wiktku-start-form textarea,
#wiktku-start-form select {
font-family: var(--wikt-sans);
font-size: 15px;
color: var(--wikt-ink);
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
padding: 10px 14px;
box-shadow: none;
transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
#wiktku-start-form input[type="text"],
#wiktku-start-form input[type="search"],
#wiktku-start-form select {
min-height: var(--wikt-tap);
}
#wiktku-start-form textarea {
line-height: 1.6;
}
/* Wikitext and anything template-shaped reads as code. */
#wiktku-start-form .wiktku-posparams-text,
#wiktku-start-form .wiktku-pron-text,
#wiktku-start-form .wiktku-etym-text,
#wiktku-start-form .wiktku-cavkani-text,
#wiktku-start-form .wiktku-tewandin-text,
#wiktku-start-form .wiktku-pron-preview-body {
font-family: var(--wikt-mono);
font-size: 13.5px;
background: var(--wikt-code-bg);
border-color: var(--wikt-card-edge);
}
#wiktku-start-form ::placeholder {
color: var(--wikt-placeholder);
opacity: 1;
}
/* Focus: accent edge + 3px tint halo. The only "elevation" in the design. */
#wiktku-start-form input[type="text"]:focus,
#wiktku-start-form input[type="search"]:focus,
#wiktku-start-form textarea:focus,
#wiktku-start-form select:focus,
#wiktku-start-form input.wiktku-ac-input:focus {
border-color: var(--wikt-accent);
box-shadow: 0 0 0 3px var(--wikt-accent-tint);
outline: 0;
}
/* The searchable picker's visible input is the field, so it takes the
field shape too (it otherwise inherits an older, boxier rule). */
#wiktku-start-form input.wiktku-ac-input {
min-height: var(--wikt-tap);
border-radius: var(--wikt-r-field);
border-color: var(--wikt-field-edge);
font-size: 15px;
}
#wiktku-start-form .wiktku-ac-results {
border: 1px solid var(--wikt-rule);
border-radius: var(--wikt-r-field);
background: var(--wikt-card);
box-shadow: none;
}
#wiktku-start-form .wiktku-ac-results li.wiktku-ac-active,
#wiktku-start-form .wiktku-ac-results li:hover {
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
}
/* ── Tabs → the accent's main job ────────────────────────────────────
Active tab: tint fill + 3px accent underline, per the spec's
"xala hûrgulî" note. Inactive tabs are plain text. */
#wiktku-start-form #wiktku-group-tabs,
#wiktku-start-form #wiktku-section-tabs {
display: flex;
flex-wrap: wrap;
gap: 4px;
align-items: center;
border-bottom: 1px solid var(--wikt-rule);
margin-bottom: 16px;
padding-bottom: 0;
}
/* Both bars are direct children of #wiktku-start-form, so a sticky child
stays stuck for as long as any part of the form is in view. Background
is opaque so content scrolling underneath doesn't show through.
Section-tabs pins below --wiktku-siteheader-h — an offset for Vector
2022's own sticky header, which otherwise sits on top of it at top:0
(Minerva has no such header, so mobile needs no offset); group-tabs
pins below that plus --wiktku-stabs-h (section-tabs' own height). Both
custom properties are set by syncStickyTabsOffset() in start.js and
fall back to 0 on skins without a Vector sticky header. */
#wiktku-start-form #wiktku-section-tabs {
position: sticky;
top: var(--wiktku-siteheader-h, 0px);
z-index: 20;
background: var(--wikt-card);
}
#wiktku-start-form #wiktku-group-tabs {
position: sticky;
top: calc(var(--wiktku-siteheader-h, 0px) + var(--wiktku-stabs-h, 0px));
z-index: 19;
background: var(--wikt-card);
}
#wiktku-start-form .wiktku-group-tab,
#wiktku-start-form .wiktku-section-tab {
font-size: 13.5px;
font-weight: 500;
color: var(--wikt-ink-3);
background: transparent;
border: 0;
border-bottom: 3px solid transparent;
border-radius: var(--wikt-r-pill) var(--wikt-r-pill) 0 0;
padding: 9px 14px 11px;
cursor: pointer;
transition: background 0.12s ease, color 0.12s ease;
}
#wiktku-start-form .wiktku-group-tab:hover,
#wiktku-start-form .wiktku-section-tab:hover {
background: var(--wikt-card-sunken);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-group-tab.wiktku-tab-active,
#wiktku-start-form .wiktku-section-tab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
/* ── Progressive-disclosure toggles ──────────────────────────────────
Shared by "Tercîhên pêşketî" (posparams) and "Malûmatên pêşketî"
(Mane): a rotating chevron plus a count of what's hidden. */
#wiktku-start-form .wiktku-adv-toggle-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
flex-wrap: wrap;
margin: 4px 0 12px;
padding-top: 16px;
border-top: 1px solid var(--wikt-rule-soft);
}
#wiktku-start-form .wiktku-adv-toggle {
display: inline-flex;
align-items: center;
gap: 10px;
font-size: 14px;
font-weight: 500;
color: var(--wikt-accent);
text-decoration: none;
cursor: pointer;
}
#wiktku-start-form .wiktku-adv-toggle:hover .wiktku-adv-toggle-text {
text-decoration: underline;
}
#wiktku-start-form .wiktku-adv-toggle-chevron {
width: 22px;
height: 22px;
flex: 0 0 22px;
display: grid;
place-items: center;
font-size: 11px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
border-radius: 6px;
transform: rotate(-90deg);
transition: transform 0.15s ease;
}
/* Open state: dfn flips the class on the step, posparams on the link. */
#wiktku-start-form .wiktku-adv-toggle.wiktku-adv-open .wiktku-adv-toggle-chevron,
#wiktku-start-dfn:not(.wiktku-dfn-subs-hidden) .wiktku-adv-toggle-chevron {
transform: rotate(0deg);
}
#wiktku-start-form .wiktku-posparams-adv-panel {
border: 1px solid var(--wikt-rule-soft);
border-radius: 12px;
background: var(--wikt-card-sunken);
padding: 20px 18px;
margin-bottom: 4px;
}
/* Vertical rhythm between stacked field rows (e.g. "Tewandina lêkerê"),
wider than a row's own label-to-field gap so rows read as distinct
groups instead of bleeding together. */
#wiktku-start-form .wiktku-posparams-helpers {
display: flex;
flex-direction: column;
gap: 14px;
}
/* ── posparams grid ──────────────────────────────────────────────────
"Tor, ne rêz" — two columns, nothing narrower than 220px, so the six
gender/number fields stop being a six-across scanline. */
@media (min-width: 721px) {
#wiktku-start-form .wiktku-posparams-helpers,
#wiktku-start-form .wiktku-posparams-adv-panel {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 18px 22px;
align-items: start;
}
#wiktku-start-form .wiktku-posparams-adv-panel > .wiktku-posparams-row,
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-posparams-row {
flex-direction: column;
align-items: stretch;
flex-wrap: nowrap;
gap: 7px;
}
/* The row's own gap already separates the label from its field — the
label's margin-bottom (set for the stacked mobile layout) would
otherwise double that spacing here. */
#wiktku-start-form .wiktku-posparams-adv-panel > .wiktku-posparams-row label,
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-posparams-row label {
margin-bottom: 0;
}
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-adv-toggle-row,
#wiktku-start-form .wiktku-posparams-helpers > .wiktku-posparams-adv-panel,
#wiktku-start-form .wiktku-posparams-adv-panel > .wiktku-posparams-opt-panel {
grid-column: 1 / -1;
}
}
#wiktku-start-form .wiktku-posparams-opt-panel {
border-top: 1px solid var(--wikt-rule-soft);
padding-top: 14px;
margin-top: 4px;
}
#wiktku-start-form .wiktku-posparams-opt-row {
font-size: 13px;
color: var(--wikt-ink-3);
}
/* ── Sense blocks ────────────────────────────────────────────────────
Each sense is a sunken panel inside the Mane card, with its number as
an accent counter — so "how many meanings am I writing" is legible at
a glance. */
#wiktku-start-form .wiktku-dfn-subgroup {
margin: 14px 0 0;
padding-top: 14px;
border-top: 1px solid var(--wikt-rule-soft);
}
/* No divider above the first item — it would be a rule against the
sense's own text. Needed explicitly: the earlier :first-of-type rule
is a class selector and loses to this id-scoped one. */
#wiktku-start-form .wiktku-dfn-subgroup:first-of-type {
margin-top: 8px;
padding-top: 0;
border-top: none;
}
#wiktku-start-form .wiktku-dfn-sublabel-term {
color: var(--wikt-ink);
}
/* Plain white field like any other, not a tinted fill — a tint would
read as "disabled" next to the white Mane box above it. */
#wiktku-start-form .wiktku-dfn-subfield,
#wiktku-start-form textarea.wiktku-dfn-mane,
#wiktku-start-form .wiktku-word-input {
background: var(--wikt-field-bg);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-field);
}
/* No "1." "2." markers on the sub-lists: numbered rows would read as a
form to fill top-down, when each row is really "another one if you
have one" — and the marker would land on its own line above the
block-level textarea, looking like a stray character. */
#wiktku-start-form .wiktku-dfn-sublist {
margin: 0;
padding-left: 0;
list-style: none;
}
#wiktku-start-form .wiktku-dfn-sublist > li {
margin-bottom: 6px;
}
/* ── Chips: tags, modifiers, template pills ──────────────────────────── */
#wiktku-start-form .wiktku-mod-pill,
#wiktku-start-form .wiktku-besendin-select + .wiktku-chip {
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 13px;
padding: 6px 12px;
}
#wiktku-start-form .wiktku-mod-btn,
#wiktku-start-form .wiktku-mod-confirm,
#wiktku-start-form .wiktku-mod-cancel {
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-radius: 8px;
}
/* ── Chip + toggle fields ("1c" design) — Dever/Şîve, Etîket, Kategorî:
a small "+ …" toggle on the sense, not an always-visible field (see
buildChipField in start.js, used by all three). Chips sit inline before
the toggle; clicking it opens a compact panel with optional quick-pick
suggestions plus free text below. Each field's own wrap class (e.g.
.wiktku-dfn-dever-wrap) only scopes chip reads/writes in start.js — all
the visual styling here is shared via .wiktku-dfn-chipfield-*. The three
toggles live together in .wiktku-dfn-chipfield-row; their panels render
separately in .wiktku-dfn-chipfield-panels below it, so opening one
field's panel never pushes the other toggles around. ── */
#wiktku-start-form .wiktku-dfn-chipfield-row {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin: 8px 0;
}
#wiktku-start-form .wiktku-dfn-chipfield-panels {
display: flex;
flex-direction: column;
gap: 8px;
}
#wiktku-start-form .wiktku-dfn-chipfield-wrap {
margin: 0;
}
#wiktku-start-form .wiktku-dfn-chipfield-chips {
display: inline-flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
#wiktku-start-form .wiktku-dfn-chipfield-chip {
display: inline-flex;
align-items: center;
gap: 6px;
background: var(--wikt-accent-tint);
color: var(--wikt-accent-deep);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-pill);
font-size: 12px;
padding: 3px 6px 3px 10px;
}
#wiktku-start-form .wiktku-dfn-chipfield-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
#wiktku-start-form .wiktku-dfn-chipfield-remove:hover {
color: var(--wikt-danger);
}
#wiktku-start-form .wiktku-dfn-chipfield-toggle {
font-size: 12px;
color: var(--wikt-ink-2);
background: var(--wikt-card);
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
padding: 3px 10px;
cursor: pointer;
}
#wiktku-start-form .wiktku-dfn-chipfield-toggle:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
#wiktku-start-form .wiktku-dfn-chipfield-panel {
margin-top: 8px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-card);
border-radius: var(--wikt-r-card, 6px);
padding: 12px;
display: flex;
flex-direction: column;
gap: 10px;
/* max-width bounds .wiktku-dfn-chipfield-suggest so its pills (e.g.
Dever/Şîve's 11 suggestions) wrap onto multiple lines instead of
stretching the panel into one long unbroken row. */
min-width: 260px;
max-width: 340px;
}
#wiktku-start-form .wiktku-dfn-chipfield-hint {
font-size: 13px;
color: var(--wikt-ink-2);
}
#wiktku-start-form .wiktku-dfn-chipfield-suggest {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
#wiktku-start-form .wiktku-dfn-chipfield-pill {
display: inline-flex;
align-items: center;
gap: 4px;
font-size: 12px;
cursor: pointer;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
padding: 3px 8px 3px 10px;
}
#wiktku-start-form .wiktku-dfn-chipfield-pill-remove {
border: 0;
background: transparent;
color: var(--wikt-muted);
font-size: 13px;
line-height: 1;
cursor: pointer;
padding: 0 2px;
}
#wiktku-start-form .wiktku-dfn-chipfield-pill-remove:hover {
color: var(--wikt-danger);
}
#wiktku-start-form .wiktku-dfn-chipfield-pill:hover {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent-edge);
color: var(--wikt-accent-deep);
}
#wiktku-start-form .wiktku-dfn-chipfield-input-row {
display: flex;
gap: 6px;
}
#wiktku-start-form .wiktku-dfn-chipfield-input {
flex: 1;
/* Without this, flexbox gives a text input an automatic minimum size
based on its default content width, which is wider than this panel
ever gets on a phone — the row can't shrink to fit and "Qeyd bike"
gets pushed out past the card's edge instead of wrapping/shrinking. */
min-width: 0;
border: 1px solid var(--wikt-field-edge);
border-radius: 2px;
font-size: 13px;
padding: 6px 8px;
background: var(--wikt-card);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-dfn-chipfield-add {
flex-shrink: 0;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
background: var(--wikt-card);
color: var(--wikt-ink-2);
font-family: var(--wikt-sans);
font-size: 13px;
padding: 6px 14px;
cursor: pointer;
}
#wiktku-start-form .wiktku-dfn-chipfield-add:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
/* ── Footer + buttons ────────────────────────────────────────────────
One primary filled pill; destructive actions are outline-only in the
danger colour so they stay quiet next to it. */
#wiktku-start-form #wiktku-start-footer {
background: var(--wikt-card-sunken);
border-top: 1px solid var(--wikt-rule);
border-radius: 0 0 var(--wikt-r-card) var(--wikt-r-card);
padding: 16px 20px;
gap: 10px;
}
#wiktku-start-form #wiktku-start-footer .oo-ui-buttonElement-button {
border-radius: var(--wikt-r-pill) !important;
font-family: var(--wikt-sans);
font-size: 14px;
font-weight: 500;
padding: 11px 22px;
box-shadow: none !important;
min-height: var(--wikt-tap);
}
#wiktku-start-form #wiktku-unhide-editform .oo-ui-buttonElement-button {
background: var(--wikt-accent) !important;
border-color: var(--wikt-accent) !important;
color: var(--wikt-on-accent) !important;
}
/* Destructive actions: outline, not fill. */
#wiktku-start-form #wiktku-start-footer .oo-ui-flaggedElement-destructive .oo-ui-buttonElement-button {
background: var(--wikt-card) !important;
border-color: var(--wikt-danger) !important;
color: var(--wikt-danger) !important;
}
#wiktku-start-form #wiktku-last-saved {
font-family: var(--wikt-mono);
font-size: 12px;
color: var(--wikt-muted);
}
/* ── Beşên din picker ────────────────────────────────────────────────
This dropdown is now the entry point to eighteen sections, so it gets
room and a label rather than sitting flush under the tab bar. */
#wiktku-start-form .wiktku-besendin-picker {
margin: 0 0 20px;
padding: 16px 18px;
background: var(--wikt-card);
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
}
#wiktku-start-form .wiktku-besendin-picker .wiktku-native-select-wrap,
#wiktku-start-form .wiktku-besendin-picker input.wiktku-ac-input {
width: 100%;
max-width: 28em;
}
/* ── Notices ─────────────────────────────────────────────────────────── */
#wiktku-start-form .wiktku-addlang-notice {
background: var(--wikt-accent-tint);
border: 1px solid var(--wikt-accent-edge);
border-radius: var(--wikt-r-field);
color: var(--wikt-ink-2);
padding: 12px 16px;
margin-bottom: 16px;
}
#wiktku-start-form .wiktku-pron-preview {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent-edge);
border-radius: 12px;
}
#wiktku-start-form .wiktku-pron-preview-confirm,
#wiktku-start-form .wiktku-pron-preview-cancel {
border-radius: var(--wikt-r-pill);
padding: 8px 16px;
min-height: 36px;
}
/* Links carry the accent and a soft underline, per the spec's `a` rule. */
#wiktku-start-form a {
color: var(--wikt-accent);
text-decoration: none;
border-bottom: 1px solid var(--wikt-link-under);
}
#wiktku-start-form a:hover {
color: var(--wikt-accent-deep);
}
#wiktku-start-form .wiktku-adv-toggle,
#wiktku-start-form .wiktku-group-tab,
#wiktku-start-form .wiktku-section-tab {
border-bottom-style: solid;
}
#wiktku-start-form .wiktku-adv-toggle {
border-bottom: 0;
}
/* ===================================================================
Dark mode
===================================================================
Every colour above is a token on #wiktku-start-form, so this is a
swap of that one block.
Only applies given an explicit dark signal (skin-theme-clientpref-night /
skin-nightmode, or skin-theme-clientpref-os plus prefers-color-scheme:
dark) — no bare prefers-color-scheme fallback, since legacy Vector sets
no theme class either and would otherwise go dark with nothing else on
the page to match.
Palette is warm, not neutral grey, to keep the cream design's character.
Accent lightens to #7ba7e8 since #2a5db0 on a dark card is ~2.4:1
contrast and reads as near-black.
=================================================================== */
html.skin-theme-clientpref-night #wiktku-start-form,
html.skin-nightmode #wiktku-start-form {
--wikt-page: #171613;
--wikt-card: #201e1a;
--wikt-card-sunken: #1a1916;
--wikt-card-edge: #322e28;
--wikt-rule: #3a3630;
--wikt-rule-soft: #2c2924;
--wikt-field-edge: #4a453d;
--wikt-field-bg: #191815;
--wikt-code-bg: #14130f;
--wikt-ink: #ece7dd;
--wikt-ink-2: #d5cfc4;
--wikt-ink-3: #b3aca1;
--wikt-muted: #8f887c;
--wikt-placeholder: #6d675e;
--wikt-accent: #7ba7e8;
--wikt-accent-deep: #cfe0f8;
--wikt-accent-tint: #1c2839;
--wikt-accent-edge: #35496b;
--wikt-danger: #e5907c;
--wikt-on-accent: #14213a;
--wikt-link-under: rgba(123, 167, 232, 0.4);
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os #wiktku-start-form {
--wikt-page: #171613;
--wikt-card: #201e1a;
--wikt-card-sunken: #1a1916;
--wikt-card-edge: #322e28;
--wikt-rule: #3a3630;
--wikt-rule-soft: #2c2924;
--wikt-field-edge: #4a453d;
--wikt-field-bg: #191815;
--wikt-code-bg: #14130f;
--wikt-ink: #ece7dd;
--wikt-ink-2: #d5cfc4;
--wikt-ink-3: #b3aca1;
--wikt-muted: #8f887c;
--wikt-placeholder: #6d675e;
--wikt-accent: #7ba7e8;
--wikt-accent-deep: #cfe0f8;
--wikt-accent-tint: #1c2839;
--wikt-accent-edge: #35496b;
--wikt-danger: #e5907c;
--wikt-on-accent: #14213a;
--wikt-link-under: rgba(123, 167, 232, 0.4);
}
}
/* A handful of rules still name literal light colours instead of tokens;
neutralise the ones that actually show through in night mode. */
html.skin-theme-clientpref-night #wiktku-start-form .wiktku-ac-results,
html.skin-nightmode #wiktku-start-form .wiktku-ac-results {
background: var(--wikt-card);
border-color: var(--wikt-rule);
color: var(--wikt-ink);
}
@media screen and (prefers-color-scheme: dark) {
html.skin-theme-clientpref-os #wiktku-start-form .wiktku-ac-results {
background: var(--wikt-card);
border-color: var(--wikt-rule);
color: var(--wikt-ink);
}
}
/* ===================================================================
Autocomplete list: stop it escaping its card
===================================================================
A fixed width would run past the card's border on full-width pickers,
since the list is absolutely positioned at the wrapper's left edge.
width:100% ties it to the (shrink-to-fit) wrapper's input width instead;
min/max-width keep short pickers readable and stop viewport overflow. */
#wiktku-start-form ul.wiktku-ac-results {
width: 100%;
min-width: min(18em, 100%);
max-width: min(92vw, 34em);
box-sizing: border-box;
border-radius: var(--wikt-r-field);
box-shadow: none;
}
/* And the wrapper itself must not exceed the card it sits in. */
#wiktku-start-form .wiktku-native-select-wrap {
max-width: 100%;
}
#wiktku-start-form .wiktku-cavkani-wrap .wiktku-native-select-wrap,
#wiktku-start-form .wiktku-cavkani-wrap input.wiktku-ac-input,
#wiktku-start-form .wiktku-pron-wrap .wiktku-native-select-wrap,
#wiktku-start-form .wiktku-etym-wrap .wiktku-native-select-wrap {
width: 100%;
max-width: 100%;
box-sizing: border-box;
}
/* ===================================================================
Mobile footer
===================================================================
Below 720px the primary button keeps its full size and everything
else shrinks to a secondary scale, so the sticky footer doesn't
dominate a phone viewport.
=================================================================== */
@media (max-width: 720px) {
#wiktku-start-form {
padding: 12px;
font-size: 14px;
}
#wiktku-start-form .wiktku-start-step {
padding: 16px 14px 14px;
border-radius: 12px;
}
#wiktku-start-form .wiktku-start-step-name {
font-size: 20px !important;
}
#wiktku-start-form #wiktku-start-footer {
padding: 10px 12px;
gap: 6px;
row-gap: 8px;
}
#wiktku-start-form #wiktku-start-footer .oo-ui-buttonElement-button {
font-size: 13px;
padding: 8px 14px;
min-height: 38px;
}
/* The one you actually press stays thumb-sized. */
#wiktku-start-form #wiktku-unhide-editform .oo-ui-buttonElement-button {
font-size: 14px;
padding: 10px 20px;
min-height: var(--wikt-tap);
}
/* Secondary actions share a row instead of each claiming a full one. */
#wiktku-start-form #wiktku-start-footer .oo-ui-buttonElement {
flex: 0 1 auto;
}
#wiktku-start-form #wiktku-last-saved {
flex: 1 0 100%;
order: 10;
font-size: 11px;
white-space: normal;
}
#wiktku-start-form .wiktku-adv-toggle-row {
gap: 8px;
}
}
/* ===================================================================
IPA character palette (v3 spec)
===================================================================
Click-to-insert chart under Bilêvkirin, after fr.wiktionary's. Two
sub-tabs — a short Kurmancî set that opens by default, and the full
chart — because ~20 of the ~180 IPA symbols ever occur in Kurmancî
and showing all of them first is the same "wall of fields" problem
this redesign exists to fix.
=================================================================== */
#wiktku-start-form .wiktku-ipa-panel {
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
background: var(--wikt-card-sunken);
padding: 0 0 12px;
margin-bottom: 8px;
overflow: hidden;
}
#wiktku-start-form .wiktku-ipa-subtabs {
display: flex;
align-items: center;
gap: 4px;
padding: 8px 12px 0;
border-bottom: 1px solid var(--wikt-rule-soft);
margin-bottom: 12px;
flex-wrap: wrap;
}
#wiktku-start-form .wiktku-ipa-subtab {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink-3);
padding: 7px 12px 9px;
border-bottom: 3px solid transparent;
border-radius: 8px 8px 0 0;
cursor: pointer;
}
#wiktku-start-form .wiktku-ipa-subtab:hover {
background: var(--wikt-card);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-ipa-subtab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
/* Templates are not characters, so they sit apart from the chart —
pinned to the right of the sub-tab row. */
#wiktku-start-form .wiktku-ipa-tplbtns {
margin-left: auto;
display: flex;
gap: 6px;
padding-bottom: 6px;
}
#wiktku-start-form .wiktku-ipa-tplbtn {
font-family: var(--wikt-mono);
font-size: 12px;
padding: 6px 10px;
border-radius: 7px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
cursor: pointer;
white-space: nowrap;
}
#wiktku-start-form .wiktku-ipa-tplbtn:hover {
border-color: var(--wikt-accent);
}
#wiktku-start-form .wiktku-ipa-grid {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 12px;
}
#wiktku-start-form .wiktku-ipa-row {
display: flex;
align-items: flex-start;
gap: 10px;
}
#wiktku-start-form .wiktku-ipa-rowlabel {
flex: 0 0 74px;
font-size: 11.5px;
color: var(--wikt-muted);
padding-top: 8px;
line-height: 1.3;
}
#wiktku-start-form .wiktku-ipa-keys {
display: flex;
flex-wrap: wrap;
gap: 4px;
flex: 1 1 auto;
}
/* Full IPA chart: a dropdown picks one group (Bêdeng 1, Bêdeng 2, …)
instead of showing all ~180 symbols' rows at once. */
#wiktku-start-form .wiktku-ipa-group-row {
padding-bottom: 4px;
}
#wiktku-start-form select.wiktku-ipa-group-select {
width: 14em;
max-width: 100%;
}
#wiktku-start-form .wiktku-ipa-key {
width: 30px;
height: 30px;
padding: 0;
border-radius: 8px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-field-bg);
color: var(--wikt-ink);
font-family: var(--wikt-mono);
font-size: 14px;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
transition: background 0.1s ease, border-color 0.1s ease;
}
#wiktku-start-form .wiktku-ipa-key:hover {
background: var(--wikt-accent-tint);
border-color: var(--wikt-accent);
color: var(--wikt-accent-deep);
}
#wiktku-start-form .wiktku-ipa-key:active {
transform: translateY(1px);
}
/* Ligatures like d͡ʒ and long vowels need a little more room than one
glyph; let those keys grow instead of clipping. */
#wiktku-start-form .wiktku-ipa-key {
width: auto;
min-width: 30px;
padding: 0 5px;
}
/* A combining mark is shown on a dotted circle so it has something to
attach to; only the mark itself is inserted. */
#wiktku-start-form .wiktku-ipa-key-combining {
color: var(--wikt-ink-2);
}
@media (max-width: 720px) {
#wiktku-start-form .wiktku-ipa-row {
flex-direction: column;
gap: 3px;
}
#wiktku-start-form .wiktku-ipa-rowlabel {
flex: 0 0 auto;
padding-top: 0;
}
#wiktku-start-form .wiktku-ipa-key {
min-width: 34px;
height: 34px;
}
}
/* ── "+ Beş zêde bike" menu ──────────────────────────────────────────
Adding a part of speech and adding Bilêvkirin are different actions,
so the button opens a small menu instead of doing one of them. */
#wiktku-start-form .wiktku-addsection-menu {
position: absolute;
z-index: 60;
margin-top: 6px;
min-width: 15em;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: 10px;
padding: 4px;
display: flex;
flex-direction: column;
}
#wiktku-start-form .wiktku-addsection-item {
display: flex;
flex-direction: column;
gap: 1px;
padding: 8px 10px;
border-radius: 7px;
border-bottom: 0;
text-decoration: none;
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-addsection-item:hover {
background: var(--wikt-accent-tint);
}
#wiktku-start-form .wiktku-addsection-item-label {
font-size: 13.5px;
font-weight: 500;
}
#wiktku-start-form .wiktku-addsection-item-hint {
font-size: 11.5px;
color: var(--wikt-muted);
}
#wiktku-start-form #wiktku-section-tabs .oo-ui-buttonElement {
position: relative;
}
/* Bilêvkirin is a peer of the POS sections but not a part of speech —
a little extra space before it marks the boundary, no divider line. */
#wiktku-start-form .wiktku-section-tab-pron {
margin-left: 6px;
}
/* ===================================================================
Per-sense tabs
===================================================================
"Mane 1 / Mane 2 / + Mane zêde bike" switches which sense is shown;
only the active one is in the flow, so a multi-sense entry doesn't
turn into a long scroll. */
#wiktku-start-form .wiktku-dfn-sense-tabs {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 4px;
border-bottom: 1px solid var(--wikt-rule);
margin-bottom: 14px;
}
#wiktku-start-form .wiktku-dfn-sense-tab {
font-size: 13px;
font-weight: 500;
color: var(--wikt-ink-3);
background: transparent;
border: 0;
border-bottom: 3px solid transparent;
border-radius: var(--wikt-r-pill) var(--wikt-r-pill) 0 0;
padding: 7px 12px 9px;
cursor: pointer;
user-select: none;
transition: background 0.12s ease, color 0.12s ease;
}
#wiktku-start-form .wiktku-dfn-sense-tab:hover {
background: var(--wikt-card-sunken);
color: var(--wikt-ink);
}
#wiktku-start-form .wiktku-dfn-sense-tab.wiktku-tab-active {
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom-color: var(--wikt-accent);
}
/* Delete button on the Mane / Beş tab bars — a real OOUI ButtonWidget
(icon: "trash", flags: ["destructive"], framed: false), so its glyph,
red destructive tint and hover/focus states all come from OOUI's own
WikimediaUI theme rather than being reimplemented here. This class
only places it: a little breathing room from the tab before it, and
vertically centered in the flex tab bar (OOUI's own icon-button box
is already close to the tabs' height, so no extra sizing is needed). */
#wiktku-start-form .wiktku-trash-btn {
margin-left: 4px;
}
#wiktku-start-form li.wiktku-dfn-sense {
border: 1px solid var(--wikt-card-edge);
border-radius: 12px;
background: var(--wikt-card);
margin-bottom: 10px;
padding: 14px;
list-style: none;
overflow: visible;
}
#wiktku-start-form li.wiktku-dfn-sense.wiktku-sense-collapsed {
display: none;
}
/* ===================================================================
Template rows in the pickers (v3 spec)
===================================================================
One row shape everywhere a template is chosen: the ⓘ explains terms
like "guharto/kevnbûyî/ku-Latn/şaşnivîs" without leaving the form,
and the 🔗 links out for when it doesn't.
=================================================================== */
#wiktku-start-form ul.wiktku-ac-results li.wiktku-ac-tplrow {
display: flex;
align-items: center;
gap: 8px;
min-height: 44px;
white-space: normal;
padding: 6px 10px;
}
#wiktku-start-form .wiktku-ac-tpltext {
flex: 1 1 auto;
min-width: 0;
font-family: var(--wikt-mono);
font-size: 13px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
/* Status, not category: green is the one to reach for, yellow means
"only if you specifically need it" (kevnbûyî, şaşnivîs, kêmpeyda). */
#wiktku-start-form .wiktku-tpl-dot {
flex: 0 0 8px;
width: 8px;
height: 8px;
border-radius: 999px;
}
#wiktku-start-form .wiktku-tpl-dot-ok { background: #3d8a55; }
#wiktku-start-form .wiktku-tpl-dot-special { background: #c9971f; }
#wiktku-start-form .wiktku-tpl-icons {
flex: 0 0 auto;
display: flex;
gap: 5px;
align-items: center;
}
#wiktku-start-form .wiktku-tpl-icon {
width: 26px;
height: 26px;
display: grid;
place-items: center;
font-size: 12px;
line-height: 1;
cursor: pointer;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
color: var(--wikt-accent);
border-bottom: 1px solid var(--wikt-accent-edge);
text-decoration: none;
padding: 0;
}
#wiktku-start-form .wiktku-tpl-icon-link { border-radius: 7px; }
#wiktku-start-form .wiktku-tpl-icon-info {
border-radius: 999px;
font-family: var(--wikt-serif);
font-style: italic;
font-weight: 600;
background: transparent;
border-color: var(--wikt-field-edge);
color: var(--wikt-muted);
}
#wiktku-start-form .wiktku-tpl-icon:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
/* ── The ⓘ panel ─────────────────────────────────────────────────────
Three parts, in the spec's order: what it does, its parameters, and a
way out to the template page. */
#wiktku-start-form .wiktku-tpl-info {
position: relative;
margin: 8px 0 4px;
padding: 14px 16px;
border: 1px solid var(--wikt-accent-edge);
background: var(--wikt-accent-tint);
border-radius: 12px;
font-size: 13.5px;
line-height: 1.6;
color: var(--wikt-ink-2);
}
#wiktku-start-form .wiktku-tpl-info-name {
font-family: var(--wikt-mono);
font-size: 13px;
color: var(--wikt-accent-deep);
margin-bottom: 6px;
}
#wiktku-start-form .wiktku-tpl-info-params {
margin-top: 8px;
}
#wiktku-start-form .wiktku-tpl-info-params-title {
font-size: 12px;
color: var(--wikt-muted);
margin-bottom: 2px;
}
#wiktku-start-form .wiktku-tpl-info-params ul {
margin: 0;
padding-left: 18px;
font-family: var(--wikt-mono);
font-size: 12.5px;
}
#wiktku-start-form .wiktku-tpl-info-link {
display: inline-block;
margin-top: 10px;
font-size: 13px;
}
#wiktku-start-form .wiktku-tpl-info-close {
position: absolute;
top: 8px;
right: 8px;
width: 24px;
height: 24px;
border-radius: 999px;
border: 0;
background: transparent;
color: var(--wikt-muted);
cursor: pointer;
font-size: 13px;
line-height: 1;
}
#wiktku-start-form .wiktku-tpl-info-close:hover {
background: var(--wikt-card);
color: var(--wikt-ink);
}
/* ===================================================================
Footer overflow "⋯" (v3 spec)
===================================================================
Bişîne and "Edîtora çavkaniyê veke" stay on the bar; the cache
actions, saved-at note and Tercîh move in here so the destructive
buttons don't compete for attention and the sticky footer stays
to one row on a phone.
=================================================================== */
#wiktku-start-form .wiktku-more-wrap {
position: relative;
margin-left: auto;
flex: 0 0 auto;
}
#wiktku-start-form .wiktku-more-btn {
width: 36px;
height: 36px;
border-radius: 999px;
border: 1px solid var(--wikt-field-edge);
background: var(--wikt-card);
color: var(--wikt-muted);
font-size: 16px;
line-height: 1;
cursor: pointer;
display: grid;
place-items: center;
}
#wiktku-start-form .wiktku-more-btn:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
}
#wiktku-start-form .wiktku-more-menu {
position: absolute;
right: 0;
bottom: calc(100% + 8px);
z-index: 70;
min-width: 17em;
background: var(--wikt-card);
border: 1px solid var(--wikt-rule);
border-radius: 12px;
padding: 6px;
display: flex;
flex-direction: column;
gap: 2px;
}
#wiktku-start-form .wiktku-more-item {
display: flex;
align-items: center;
gap: 8px;
}
/* The buttons inside the menu are menu rows, not buttons on a bar. */
#wiktku-start-form .wiktku-more-item .oo-ui-buttonElement {
flex: 1 1 auto;
}
#wiktku-start-form .wiktku-more-item .oo-ui-buttonElement-button {
display: block;
width: 100%;
text-align: left;
border: 0 !important;
background: transparent !important;
border-radius: 7px !important;
padding: 9px 10px;
min-height: 0;
font-weight: 400;
color: var(--wikt-ink) !important;
}
#wiktku-start-form .wiktku-more-item .oo-ui-buttonElement-button:hover {
background: var(--wikt-accent-tint) !important;
}
#wiktku-start-form .wiktku-more-item-danger .oo-ui-buttonElement-button {
color: var(--wikt-danger) !important;
}
#wiktku-start-form .wiktku-more-item-prefs .wiktku-prefs-btn {
width: 26px;
height: 26px;
flex: 0 0 26px;
border: 0;
background-color: transparent;
}
#wiktku-start-form .wiktku-more-item-label {
font-size: 13.5px;
}
#wiktku-start-form .wiktku-more-sep {
height: 1px;
background: var(--wikt-rule-soft);
margin: 4px 2px;
}
#wiktku-start-form .wiktku-more-note {
padding: 4px 10px 2px;
}
#wiktku-start-form .wiktku-more-note #wiktku-last-saved {
white-space: normal;
}
/* "+ Mane zêde bike" sits as the last item in .wiktku-dfn-sense-tabs. */
#wiktku-start-form .wiktku-add-sense {
display: inline-flex;
align-items: center;
gap: 6px;
margin: 3px 0;
padding: 6px 14px;
border: 1px solid var(--wikt-field-edge);
border-radius: var(--wikt-r-pill);
background: var(--wikt-card);
color: var(--wikt-ink-2);
font-family: var(--wikt-sans);
font-size: 13px;
cursor: pointer;
min-height: 34px;
}
#wiktku-start-form .wiktku-add-sense:hover {
border-color: var(--wikt-accent);
color: var(--wikt-accent);
background: var(--wikt-accent-tint);
}
#wiktku-start-form .wiktku-add-sense-plus {
color: var(--wikt-accent);
font-size: 15px;
line-height: 1;
}
/* "+ Beşeke din zêde bike" and "Bi otomatîk tijî bike" are OO.ui.ButtonWidgets
(see start.js), which otherwise render with OOUI's own framed chrome —
override the inner .oo-ui-buttonElement-button to match .wiktku-add-sense's
flat-pill shape above instead, same !important pattern already used for
#wiktku-start-footer's buttons. */
#wiktku-start-form .wiktku-add-sense-btn .oo-ui-buttonElement-button {
border: 1px solid var(--wikt-field-edge) !important;
border-radius: var(--wikt-r-pill) !important;
background: var(--wikt-card) !important;
color: var(--wikt-ink-2) !important;
font-family: var(--wikt-sans);
font-size: 13px;
font-weight: normal;
padding: 6px 14px;
min-height: 34px;
box-shadow: none !important;
}
#wiktku-start-form .wiktku-add-sense-btn .oo-ui-buttonElement-button:hover {
border-color: var(--wikt-accent) !important;
color: var(--wikt-accent) !important;
background: var(--wikt-accent-tint) !important;
}
/* ── Şablon picker, demoted to a link ────────────────────────────────
Picking a template replaces the definition rather than adding to it,
so it sits above the Mane box as a quiet alternative route, hidden
behind one line of text until it is actually wanted. */
#wiktku-start-form .wiktku-dfn-tpl-link {
display: inline-block;
margin-bottom: 8px;
font-size: 12.5px;
color: var(--wikt-muted);
border-bottom: 1px solid transparent;
}
#wiktku-start-form .wiktku-dfn-tpl-link:hover {
color: var(--wikt-accent);
border-bottom-color: var(--wikt-link-under);
}
#wiktku-start-form .wiktku-dfn-tpl-row {
margin-bottom: 10px;
padding-bottom: 12px;
border-bottom: 1px solid var(--wikt-rule-soft);
}
/* ── Template-row icons on touch screens ─────────────────────────────
26px is fine for a mouse and too small for a thumb, and these two sit
right next to each other at the end of a row you can also tap to
select. Give them real targets and some separation on small screens. */
@media (max-width: 720px), (pointer: coarse) {
#wiktku-start-form .wiktku-tpl-icon {
width: 34px;
height: 34px;
font-size: 14px;
}
#wiktku-start-form .wiktku-tpl-icons {
gap: 8px;
padding-left: 4px;
}
#wiktku-start-form ul.wiktku-ac-results li.wiktku-ac-tplrow {
min-height: 50px;
}
}
/* The rendered pronunciation preview is real parser output, so it needs
to sit as content rather than in the monospace wikitext treatment the
panel inherits. */
#wiktku-start-form .wiktku-pron-preview-body {
font-family: var(--wikt-sans);
font-size: 15px;
background: var(--wikt-card);
border: 1px solid var(--wikt-accent-edge);
border-radius: 8px;
padding: 10px 12px;
white-space: normal;
}
#wiktku-start-form .wiktku-pron-preview-body .IPA,
#wiktku-start-form .wiktku-pron-preview-body code,
#wiktku-start-form .wiktku-pron-preview-body tt {
font-family: var(--wikt-mono);
}
/* Parser output brings its own margins; strip the outer ones so the
preview box doesn't gain a blank first line. */
#wiktku-start-form .wiktku-pron-preview-body > *:first-child { margin-top: 0; }
#wiktku-start-form .wiktku-pron-preview-body > *:last-child { margin-bottom: 0; }
4p1wgpoqqxjod4hcg00su0jczsoiu1e
Bikarhêner:Wikihez/Gadget-test.js
2
1358395
6334316
6334303
2026-08-17T13:32:20Z
Wikihez
7344
6334316
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman were nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, s, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels". Same {code: {cat, display[, req]}} shape as
// the wiki's Lua number-type table; "display" can contain HTML (<abbr>)
// or wikilinks, so it's only used as-is in wikitext output, never dropped
// straight into an <option> (see stripMarkupForOption below).
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Strips the bit of markup "display" is allowed to contain so it's safe
// to use as plain <option> text (options can't render HTML or wikilinks):
// <abbr title="X">Y</abbr> -> "Y (X)", [[a|b]] -> "b", [[a]] -> "a".
function stripMarkupForOption(html) {
if (!html) return "";
return $.trim(
String(html)
.replace(/<abbr[^>]*title="([^"]*)"[^>]*>([\s\S]*?)<\/abbr>/gi, "$2 ($1)")
.replace(/\[\[[^\]|]*\|([^\]]*)\]\]/g, "$1")
.replace(/\[\[([^\]]*)\]\]/g, "$1")
.replace(/<[^>]+>/g, "")
);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = stripMarkupForOption(def.display);
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Xeta nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Badînî", "Serhed", "Botan", "Hekarî",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Sûriye"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîf" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "", // translation/gloss reads fine bare: "word (gloss)"
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê peyvê rake'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê taybetmendiyê rake'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê pêşniyarê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Rake'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonî hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ──────────────────────────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "b-gh", label: "biwêjî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" },
{ code: "b-ngh", label: "biwêjî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "taybetmendî" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "taybetmendî",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Lêker derbasdar e? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (bireser digire)</option>"))
.append($("<option value='ngh'>negerguhêz (bireser nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"))
.append($("<option value='b'>biwêjî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
// "form" marks this entry as a form-of/inflected entry — it
// takes "erê" rather than the plain "1" the other boolean
// opt params use.
out += "|" + key + "=" + (key === "form" ? "erê" : "1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav" || pos === "rengdêr";
var isAdj = pos === "rengdêr";
var isTîp = pos === "tîp";
var isJa = lang === "ja";
var isLeker = /^lêker/.test(pos);
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var def = NUMBER_LABELS[code] || {};
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
// The explanation (cat, e.g. "dualia tantum") shows as a
// hover tooltip — <option> can't hold richer markup.
if (def.cat) opt.attr("title", def.cat);
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Not a user-facing choice — kept in the DOM (hidden from the
// dropdown list) purely so runCitoidFetch, selectCatTpl and the
// draft-restore code below can still switch a row into manual
// mode programmatically; a native <select>'s .val() only takes
// if a matching <option> exists. People reach the manual
// textarea only via "Ji Kategorî:Çavkanî" or a Citoid fetch that
// found no matching şablon — never by picking it directly.
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over the members of [[Kategorî:Çavkanî]].
// Picking a şablon here immediately switches the row into
// the (hidden) manual mode with {{Şablon}} pre-filled,
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
block.find("." + cls + "-list textarea").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params. "form" is stored as
// "erê" rather than "1" (see rebuildTa), so it needs its
// own check here.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var expected = key === "form" ? "erê" : "1";
$(this).prop("checked", savedTa.indexOf("|" + key + "=" + expected) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions — same shape as the wiki's
// own Lua number-type table (code -> {cat, display[, req]}).
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
4in5voboepvvrs3x9wrv86d9wtq9vk4
6334317
6334316
2026-08-17T13:39:04Z
Wikihez
7344
6334317
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman were nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, s, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels". Same {code: {cat, display[, req]}} shape as
// the wiki's Lua number-type table; "display" can contain HTML (<abbr>)
// or wikilinks, so it's only used as-is in wikitext output, never dropped
// straight into an <option> (see stripMarkupForOption below).
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Strips the bit of markup "display" is allowed to contain so it's safe
// to use as plain <option> text (options can't render HTML or wikilinks):
// <abbr title="X">Y</abbr> -> "Y (X)", [[a|b]] -> "b", [[a]] -> "a".
function stripMarkupForOption(html) {
if (!html) return "";
return $.trim(
String(html)
.replace(/<abbr[^>]*title="([^"]*)"[^>]*>([\s\S]*?)<\/abbr>/gi, "$2 ($1)")
.replace(/\[\[[^\]|]*\|([^\]]*)\]\]/g, "$1")
.replace(/\[\[([^\]]*)\]\]/g, "$1")
.replace(/<[^>]+>/g, "")
);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = stripMarkupForOption(def.display);
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Xeta nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Badînî", "Serhed", "Botan", "Hekarî",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Sûriye"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîf" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "", // translation/gloss reads fine bare: "word (gloss)"
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê peyvê rake'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê taybetmendiyê rake'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê pêşniyarê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Rake'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonî hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ──────────────────────────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "b-gh", label: "biwêjî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" },
{ code: "b-ngh", label: "biwêjî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "taybetmendî" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "taybetmendî",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Lêker derbasdar e? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (bireser digire)</option>"))
.append($("<option value='ngh'>negerguhêz (bireser nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"))
.append($("<option value='b'>biwêjî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
// "form" marks this entry as a form-of/inflected entry — it
// takes "erê" rather than the plain "1" the other boolean
// opt params use.
out += "|" + key + "=" + (key === "form" ? "erê" : "1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav" || pos === "rengdêr";
var isAdj = pos === "rengdêr";
var isTîp = pos === "tîp";
var isJa = lang === "ja";
var isLeker = /^lêker/.test(pos);
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var def = NUMBER_LABELS[code] || {};
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
// The explanation (cat, e.g. "dualia tantum") shows as a
// hover tooltip — <option> can't hold richer markup.
if (def.cat) opt.attr("title", def.cat);
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Not a user-facing choice — kept in the DOM (hidden from the
// dropdown list) purely so runCitoidFetch, selectCatTpl and the
// draft-restore code below can still switch a row into manual
// mode programmatically; a native <select>'s .val() only takes
// if a matching <option> exists. People reach the manual
// textarea only via "Ji Kategorî:Çavkanî" or a Citoid fetch that
// found no matching şablon — never by picking it directly.
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over the members of [[Kategorî:Çavkanî]].
// Picking a şablon here immediately switches the row into
// the (hidden) manual mode with {{Şablon}} pre-filled,
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Scoped to the canonical field only -- makeModifierLi() (used by
// every MODIFIER_SUBGROUPS field, which is every field readList()
// is called for) wraps it with a second, visible <textarea
// class="wiktku-word-input"> mirror of the same word; matching
// bare "textarea" here picked up both and doubled every word.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params. "form" is stored as
// "erê" rather than "1" (see rebuildTa), so it needs its
// own check here.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var expected = key === "form" ? "erê" : "1";
$(this).prop("checked", savedTa.indexOf("|" + key + "=" + expected) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions — same shape as the wiki's
// own Lua number-type table (code -> {cat, display[, req]}).
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
lzavljj20xrxnpb10k5iymtj9rz3wmf
6334320
6334317
2026-08-17T13:50:29Z
Wikihez
7344
6334320
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman were nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, s, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels". Same {code: {cat, display[, req]}} shape as
// the wiki's Lua number-type table; "display" can contain HTML (<abbr>)
// or wikilinks, so it's only used as-is in wikitext output, never dropped
// straight into an <option> (see stripMarkupForOption below).
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Strips the bit of markup "display" is allowed to contain so it's safe
// to use as plain <option> text (options can't render HTML or wikilinks):
// <abbr title="X">Y</abbr> -> "Y (X)", [[a|b]] -> "b", [[a]] -> "a".
function stripMarkupForOption(html) {
if (!html) return "";
return $.trim(
String(html)
.replace(/<abbr[^>]*title="([^"]*)"[^>]*>([\s\S]*?)<\/abbr>/gi, "$2 ($1)")
.replace(/\[\[[^\]|]*\|([^\]]*)\]\]/g, "$1")
.replace(/\[\[([^\]]*)\]\]/g, "$1")
.replace(/<[^>]+>/g, "")
);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = stripMarkupForOption(def.display);
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Xeta nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Badînî", "Serhed", "Botan", "Hekarî",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Sûriye"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîf" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "", // translation/gloss reads fine bare: "word (gloss)"
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê peyvê rake'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê taybetmendiyê rake'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê pêşniyarê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Rake'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonî hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ──────────────────────────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "b-gh", label: "biwêjî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" },
{ code: "b-ngh", label: "biwêjî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "taybetmendî" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "taybetmendî",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Lêker derbasdar e? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (bireser digire)</option>"))
.append($("<option value='ngh'>negerguhêz (bireser nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"))
.append($("<option value='b'>biwêjî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
// "form" marks this entry as a form-of/inflected entry — it
// takes "erê" rather than the plain "1" the other boolean
// opt params use.
out += "|" + key + "=" + (key === "form" ? "erê" : "1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav" || pos === "rengdêr";
var isAdj = pos === "rengdêr";
var isTîp = pos === "tîp";
var isJa = lang === "ja";
var isLeker = /^lêker/.test(pos);
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var def = NUMBER_LABELS[code] || {};
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
// The explanation (cat, e.g. "dualia tantum") shows as a
// hover tooltip — <option> can't hold richer markup.
if (def.cat) opt.attr("title", def.cat);
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params. "form" is stored as
// "erê" rather than "1" (see rebuildTa), so it needs its
// own check here.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var expected = key === "form" ? "erê" : "1";
$(this).prop("checked", savedTa.indexOf("|" + key + "=" + expected) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions — same shape as the wiki's
// own Lua number-type table (code -> {cat, display[, req]}).
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
a777ni95ms77x3j7ip2c03kqi0hhzv4
6334330
6334320
2026-08-17T14:34:30Z
Wikihez
7344
6334330
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman were nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, s, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels". Same {code: {cat, display[, req]}} shape as
// the wiki's Lua number-type table; "display" can contain HTML (<abbr>)
// or wikilinks, so it's only used as-is in wikitext output, never dropped
// straight into an <option> (see stripMarkupForOption below).
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Strips the bit of markup "display" is allowed to contain so it's safe
// to use as plain <option> text (options can't render HTML or wikilinks):
// <abbr title="X">Y</abbr> -> "Y (X)", [[a|b]] -> "b", [[a]] -> "a".
function stripMarkupForOption(html) {
if (!html) return "";
return $.trim(
String(html)
.replace(/<abbr[^>]*title="([^"]*)"[^>]*>([\s\S]*?)<\/abbr>/gi, "$2 ($1)")
.replace(/\[\[[^\]|]*\|([^\]]*)\]\]/g, "$1")
.replace(/\[\[([^\]]*)\]\]/g, "$1")
.replace(/<[^>]+>/g, "")
);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = stripMarkupForOption(def.display);
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Xeta nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Badînî", "Serhed", "Botan", "Hekarî",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Sûriye"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîf" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "", // translation/gloss reads fine bare: "word (gloss)"
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê peyvê rake'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê taybetmendiyê rake'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê pêşniyarê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Rake'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonî hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ──────────────────────────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "b-gh", label: "biwêjî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" },
{ code: "b-ngh", label: "biwêjî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "taybetmendî" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "taybetmendî",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Lêker derbasdar e? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (bireser digire)</option>"))
.append($("<option value='ngh'>negerguhêz (bireser nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"))
.append($("<option value='b'>biwêjî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav" || pos === "rengdêr";
var isAdj = pos === "rengdêr";
var isTîp = pos === "tîp";
var isJa = lang === "ja";
var isLeker = /^lêker/.test(pos);
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var def = NUMBER_LABELS[code] || {};
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
// The explanation (cat, e.g. "dualia tantum") shows as a
// hover tooltip — <option> can't hold richer markup.
if (def.cat) opt.attr("title", def.cat);
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions — same shape as the wiki's
// own Lua number-type table (code -> {cat, display[, req]}).
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
q6u6qa5fm4xcnvdot2op92ibvnyogh8
6334358
6334330
2026-08-17T16:04:20Z
Wikihez
7344
6334358
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman were nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, s, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels". Same {code: {cat, display[, req]}} shape as
// the wiki's Lua number-type table; "display" can contain HTML (<abbr>)
// or wikilinks, so it's only used as-is in wikitext output, never dropped
// straight into an <option> (see stripMarkupForOption below).
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Strips the bit of markup "display" is allowed to contain so it's safe
// to use as plain <option> text (options can't render HTML or wikilinks):
// <abbr title="X">Y</abbr> -> "Y (X)", [[a|b]] -> "b", [[a]] -> "a".
function stripMarkupForOption(html) {
if (!html) return "";
return $.trim(
String(html)
.replace(/<abbr[^>]*title="([^"]*)"[^>]*>([\s\S]*?)<\/abbr>/gi, "$2 ($1)")
.replace(/\[\[[^\]|]*\|([^\]]*)\]\]/g, "$1")
.replace(/\[\[([^\]]*)\]\]/g, "$1")
.replace(/<[^>]+>/g, "")
);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = stripMarkupForOption(def.display);
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Xeta nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nm|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Badînî", "Serhed", "Botan", "Hekarî",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Sûriye"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîf" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "", // translation/gloss reads fine bare: "word (gloss)"
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê peyvê rake'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê taybetmendiyê rake'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê pêşniyarê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Rake'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonî hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ──────────────────────────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "b-gh", label: "biwêjî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" },
{ code: "b-ngh", label: "biwêjî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "taybetmendî" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "taybetmendî",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Lêker derbasdar e? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (bireser digire)</option>"))
.append($("<option value='ngh'>negerguhêz (bireser nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"))
.append($("<option value='b'>biwêjî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav" || pos === "rengdêr";
var isAdj = pos === "rengdêr";
var isTîp = pos === "tîp";
var isJa = lang === "ja";
var isLeker = /^lêker/.test(pos);
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var def = NUMBER_LABELS[code] || {};
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
// The explanation (cat, e.g. "dualia tantum") shows as a
// hover tooltip — <option> can't hold richer markup.
if (def.cat) opt.attr("title", def.cat);
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTîp);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTîp && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions — same shape as the wiki's
// own Lua number-type table (code -> {cat, display[, req]}).
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
ql6crklzb6pngjhu3nlp0yi6ssvmy6m
6334367
6334358
2026-08-17T17:34:16Z
Wikihez
7344
6334367
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var def = NUMBER_LABELS[code] || {};
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
// The explanation (cat, e.g. "dualia tantum") shows as a
// hover tooltip — <option> can't hold richer markup.
if (def.cat) opt.attr("title", def.cat);
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions — same shape as the wiki's
// own Lua number-type table (code -> {cat, display[, req]}).
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
o09839lj0eoq5p0d9m1xbu2h1dh23ht
6334370
6334367
2026-08-17T17:44:00Z
Wikihez
7344
6334370
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions — same shape as the wiki's
// own Lua number-type table (code -> {cat, display[, req]}).
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
pan45j52fm7pez0cp9nuefr0roubgon
6334373
6334370
2026-08-17T17:57:20Z
Wikihez
7344
6334373
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", safeRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
qczjtf5mifhiam46vhq3pf161dgl7uw
6334375
6334373
2026-08-17T18:14:47Z
Wikihez
7344
6334375
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var foldDiacritics = wiktkuUtil.foldDiacritics;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// refresh() walks every sense block and nimûne item, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
10iiqtmivayqmag69lh1eevhyhbxw3f
6334379
6334375
2026-08-17T19:02:25Z
Wikihez
7344
6334379
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
var panel = $("<div class='wiktku-ipa-panel'></div>").hide();
var toggle = $("<a href='#' class='wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Herfên IPAyê</span>"));
toggle.on("click", function (e) {
e.preventDefault();
var open = panel.is(":visible");
panel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
wrap.append($("<div class='wiktku-adv-toggle-row'></div>").append(toggle));
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Image line (between header and template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// refresh() walks every sense block and nimûne item, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
j8bo588jwqizczim5vh4yz3le9qqva2
6334397
6334379
2026-08-17T19:35:49Z
Wikihez
7344
6334397
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
// The short Kurmancî/full IPA chart shows directly, always expanded.
var panel = $("<div class='wiktku-ipa-panel'></div>");
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// {{ku-tewîn-lk}} line for xwerû Kurmancî verbs once niha/borî/transitivity are all answered.
var tewinLkLine = "";
if (pos === "lêker" && values.lang === "ku") {
var pp = $("#wiktku-start-posparams");
var niha = $.trim(pp.find(".wiktku-posparams-field[data-pp-key='niha']").val());
var borî = $.trim(pp.find(".wiktku-posparams-field[data-pp-key='borî']").val());
var lektur = pp.find(".wiktku-posparams-lektur-sel").val();
var lekturForm = pp.find(".wiktku-posparams-lekturform-sel").val();
var lkForm = lektur === "gh" ? "gerguhêz" : (lektur === "ngh" ? "negerguhêz" : "");
if (niha && borî && lekturForm === "x" && lkForm) {
tewinLkLine = "{{ku-tewîn-lk|" + pageName + "|form=" + lkForm + "|niha=" + niha + "|borî=" + borî + "}}\n";
}
}
// Image line (between the conjugation line, if any, and the template call)
var imageLine = steps.resim.output(values);
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
return header + tewinLkLine + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// refresh() walks every sense block and nimûne item, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
62yekrlpknq1y517jjpzq18yqntna9i
6334398
6334397
2026-08-17T19:54:52Z
Wikihez
7344
6334398
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
// The short Kurmancî/full IPA chart shows directly, always expanded.
var panel = $("<div class='wiktku-ipa-panel'></div>");
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
// {{ku-tewîn-lk}} line for xwerû Kurmancî verbs, parsed straight out of tplLine (not the posparams input fields, which a cache/section restore doesn't repopulate) once niha/borî/transitivity are all present.
var tewinLkLine = "";
if (pos === "lêker" && values.lang === "ku") {
var nihaMatch = tplLine.match(/\|niha=([^|}]+)/);
var borîMatch = tplLine.match(/\|borî=([^|}]+)/);
var cMatch = tplLine.match(/\|c=([^|}]+)/);
var niha = nihaMatch ? $.trim(nihaMatch[1]) : "";
var borî = borîMatch ? $.trim(borîMatch[1]) : "";
var cParts = cMatch ? /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]) : null;
var lekturForm = cParts ? (cParts[1] || "") : "";
var lktransitivity = cParts ? cParts[2] : "";
var lkForm = lktransitivity === "gh" ? "gerguhêz" : (lktransitivity === "ngh" ? "negerguhêz" : "");
if (niha && borî && lekturForm === "x" && lkForm) {
tewinLkLine = "{{ku-tewîn-lk|" + pageName + "|form=" + lkForm + "|niha=" + niha + "|borî=" + borî + "}}\n";
}
}
// Image line (between the conjugation line, if any, and the template call)
var imageLine = steps.resim.output(values);
return header + tewinLkLine + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// refresh() walks every sense block and nimûne item, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
fb7c2u3546lzzf7w21rlt1nyg0mu176
6334399
6334398
2026-08-17T20:04:10Z
Wikihez
7344
6334399
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
// The short Kurmancî/full IPA chart shows directly, always expanded.
var panel = $("<div class='wiktku-ipa-panel'></div>");
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
// {{ku-tewîn-lk}} line for xwerû Kurmancî verbs, parsed straight out of tplLine (not the posparams input fields, which a cache/section restore doesn't repopulate) once niha/borî/transitivity are all present.
var tewinLkLine = "";
if (pos === "lêker" && values.lang === "ku") {
var nihaMatch = tplLine.match(/\|niha=([^|}]+)/);
var borîMatch = tplLine.match(/\|borî=([^|}]+)/);
var cMatch = tplLine.match(/\|c=([^|}]+)/);
var niha = nihaMatch ? $.trim(nihaMatch[1]) : "";
var borî = borîMatch ? $.trim(borîMatch[1]) : "";
var cParts = cMatch ? /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]) : null;
var lekturForm = cParts ? (cParts[1] || "") : "";
var lktransitivity = cParts ? cParts[2] : "";
var lkForm = lktransitivity === "gh" ? "gerguhêz" : (lktransitivity === "ngh" ? "negerguhêz" : "");
if (niha && borî && lekturForm === "x" && lkForm) {
tewinLkLine = "{{ku-tewîn-lk|" + pageName + "|form=" + lkForm + "|niha=" + niha + "|borî=" + borî + "}}\n";
}
}
// Image line (between the conjugation line, if any, and the template call)
var imageLine = steps.resim.output(values);
return header + tewinLkLine + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// refresh() walks every sense block and nimûne item, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
refresh();
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
refresh();
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
hbu7ybzaojzj8t9n9f6jihble1kb5q3
6334400
6334399
2026-08-17T20:20:05Z
Wikihez
7344
6334400
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
// The short Kurmancî/full IPA chart shows directly, always expanded.
var panel = $("<div class='wiktku-ipa-panel'></div>");
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
// {{ku-tewîn-lk}} line for xwerû Kurmancî verbs, parsed straight out of tplLine (not the posparams input fields, which a cache/section restore doesn't repopulate) once niha/borî/transitivity are all present.
var tewinLkLine = "";
if (pos === "lêker" && values.lang === "ku") {
var nihaMatch = tplLine.match(/\|niha=([^|}]+)/);
var borîMatch = tplLine.match(/\|borî=([^|}]+)/);
var cMatch = tplLine.match(/\|c=([^|}]+)/);
var niha = nihaMatch ? $.trim(nihaMatch[1]) : "";
var borî = borîMatch ? $.trim(borîMatch[1]) : "";
var cParts = cMatch ? /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]) : null;
var lekturForm = cParts ? (cParts[1] || "") : "";
var lktransitivity = cParts ? cParts[2] : "";
var lkForm = lktransitivity === "gh" ? "gerguhêz" : (lktransitivity === "ngh" ? "negerguhêz" : "");
if (niha && borî && lekturForm === "x" && lkForm) {
tewinLkLine = "{{ku-tewîn-lk|" + pageName + "|form=" + lkForm + "|niha=" + niha + "|borî=" + borî + "}}\n";
}
}
// Image line (between the conjugation line, if any, and the template call)
var imageLine = steps.resim.output(values);
return header + tewinLkLine + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// refresh() walks every sense block and nimûne item, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// Multi-word page names skip the {{ji?|lang}} placeholder entirely -- "source unknown?" doesn't apply to a phrase.
if (pageName.includes(" ")) return "";
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
refresh();
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
refresh();
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter pronunciation and etymology template dropdowns when language changes.
steps.pron.refreshTplSelect();
steps.etym.refreshTplSelect();
steps.cavkani.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
extraChoices.sort(function (a, b) {
return a.sortKey.localeCompare(b.sortKey, userLang);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
61khmsv3y2g1n1e9mt7x1085gymhs97
6334407
6334400
2026-08-17T21:29:23Z
Wikihez
7344
6334407
javascript
text/javascript
// Li ser [[vi:MediaWiki:Gadget-start.js]] hatiye refaktorkirin.
// implicit dependencies: StorageUtils (ObjectStorage), Gadget-citoid-helper.js (wikt.citoid)
/* global ObjectStorage, wikt */
//<nowiki>
$(function () {
if (window.location.href.includes("editintro=User:Wikihez/creation/intro")) {
return; // Do not run the script wextê WF:Bilez dişuxile
}
var ns = mw.config.get("wgNamespaceNumber");
if (ns && ns !== 108 &&
!(ns === 2 && mw.config.get("wgPageName").match(/\/ceribandin$|\/test$/))) {
return;
}
var addLangMode = mw.util.getParamValue("wiktkuaddlang") === "1";
// ── Tercîh (user settings) panel stored via mw.storage (localStorage), globally
var WIKTKU_PREFS_KEY = "wiktku_start_prefs";
var WIKTKU_PREFS_DEFAULTS = {
// [] = show the full language list in the "ziman" step; otherwise
// only these codes are shown there (data.json codes, not names).
langCodes: [],
// true = never auto-open on a new/empty page — only via the
// "+Ziman/beşeke din" portlet link.
onlyOnClick: false,
// "auto" | "p-cactions" | "p-tb" | "p-views" — where the
// "+Ziman/beşeke din" portlet link is added.
portlet: "auto",
// true = suppress routine/informational mw.notify toasts (page
// loading, cache auto-restored, cache saved/cleared, "tê şandin…").
// Errors and direct results of an explicit action (like saving
// Tercîh itself) always show regardless of this setting.
quiet: false
};
function wiktkuLoadPrefs() {
if (!(window.mw && mw.storage)) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var raw;
try { raw = mw.storage.get(WIKTKU_PREFS_KEY); } catch (e) { raw = null; }
if (!raw) return $.extend({}, WIKTKU_PREFS_DEFAULTS);
var parsed;
try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; }
return $.extend({}, WIKTKU_PREFS_DEFAULTS, parsed);
}
function wiktkuSavePrefs(prefs) {
if (!(window.mw && mw.storage)) return false;
try {
mw.storage.set(WIKTKU_PREFS_KEY, JSON.stringify(prefs));
return true;
} catch (e) { return false; }
}
var wiktkuPrefs = wiktkuLoadPrefs();
// Routine/informational notices go through here so "quiet mode" can
// suppress them; errors and explicit-action confirmations (like the
// Tercîh save toast) call mw.notify directly instead, and always show.
function wiktkuNotify(message, options) {
if (wiktkuPrefs.quiet) return;
mw.notify(message, options);
}
// Removes every localStorage entry this script writes, across every
// page: the per-page draft caches (formCache below, one per page under
// "wiktku_start_draft_") and the per-textarea manual-resize heights
// ("wiktku_resize_height_…", set in util.js). Tercîh itself
// (WIKTKU_PREFS_KEY) is deliberately left alone — it's a setting the
// person is actively editing when they reach for this button inside the
// Tercîh dialog, not a draft/cache to discard.
//
// formDirty/autosaveDebounceTimer/formCache are declared further down
// (assigned by build()) but resolved by closure at call time, same as
// elsewhere in this file — this is only ever invoked after the wizard
// has built, since that's the only place the Tercîh button lives.
function wiktkuClearAllScriptCache() {
// Cancel the current page's pending autosave — otherwise it fires a
// few seconds later (or the periodic one does, within a minute)
// using the still-unblanked live form and silently re-writes the
// cache this just removed. Same reasoning as the single-page
// "Cache paqij bike" button.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
$("#wiktku-last-saved").text("");
// Keeps the current page's formCache instance (and its in-memory
// _cachedData) consistent with what's about to happen to its actual
// localStorage entries below.
try { formCache.Set({}); } catch (e) { /* storage unavailable — the raw sweep below still runs */ }
if (!window.localStorage) return;
var toRemove = [];
try {
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (!key) continue;
// The draft cache goes through [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage, which does NOT store it under the plain
// "wiktku_start_draft_<page>" name formCache is constructed
// with — it wraps it as "enwikt/ObjectStorage/
// wiktku_start_draft_<page>?v=<n>&Type=Data|Type". Matching
// as a substring (not a prefix) catches both the Data and
// Type entries, for every page and every cache version. The
// resize-height entries aren't wrapped — set directly via
// mw.storage in util.js — so those match as a plain prefix.
// Etîket/Dever/Kategorî suggestion overrides (WIKTKU_SUGGESTIONS_KEY_PREFIX,
// one key per field) are the same kind of direct mw.storage entry.
if (key.indexOf("wiktku_start_draft_") !== -1 || key.indexOf("wiktku_resize_height_") === 0 ||
key.indexOf(WIKTKU_SUGGESTIONS_KEY_PREFIX) === 0) {
toRemove.push(key);
}
}
} catch (e) { return; }
$.each(toRemove, function (_, key) {
try { localStorage.removeItem(key); } catch (e) { /* skip this key, keep clearing the rest */ }
});
}
// Lazily built the first time it's opened; reused after that (see
// wiktkuOpenPrefsDialog below, defined once we know OOUI is available).
var wiktkuPrefsWindowManager = null;
var wiktkuPrefsDialog = null;
function wiktkuOpenPrefsDialog() {
mw.loader.using(["oojs-ui-core", "oojs-ui-windows"], function () {
function WiktkuPrefsDialog(config) {
WiktkuPrefsDialog.super.call(this, config);
}
if (!wiktkuPrefsDialog) {
OO.inheritClass(WiktkuPrefsDialog, OO.ui.ProcessDialog);
WiktkuPrefsDialog.static.name = "wiktkuPrefsDialog";
WiktkuPrefsDialog.static.title = "Tercîhên min";
WiktkuPrefsDialog.static.actions = [
{ action: "save", label: "Qeyd bike", flags: ["primary", "progressive"] },
{ action: "cancel", label: "Betal bike", flags: "safe" },
{ action: "clearcache", label: "Hemû cache paqij bike", flags: "destructive" }
];
WiktkuPrefsDialog.prototype.initialize = function () {
WiktkuPrefsDialog.super.prototype.initialize.call(this);
this.langCodesInput = new OO.ui.TextInputWidget({
placeholder: "ku, zza, ckb (vala bihêle ji bo hemû zimanan)"
});
this.onlyOnClickCheckbox = new OO.ui.CheckboxInputWidget({});
this.quietCheckbox = new OO.ui.CheckboxInputWidget({});
this.portletDropdown = new OO.ui.DropdownInputWidget({
options: [
{ data: "auto", label: "Otomatîk (li gorî rûyê wîkiyê)" },
{ data: "p-cactions", label: "p-cactions - Li qismê Amûran" },
{ data: "left-navigation", label: "left-navigation - Li qismê Gotar-Gotûbêj" },
{ data: "p-tb", label: "p-tb - Li qismê Giştî" },
{ data: "p-views", label: "p-views - Li qismê Bixwîne" }
]
});
var fieldset = new OO.ui.FieldsetLayout({ label: "Eyar" });
fieldset.addItems([
new OO.ui.FieldLayout(this.langCodesInput, {
label: "Kodên zimanan ên ku tê xuyakirin li qismê \"ziman\"",
align: "top",
help: "Kodan bi virgulê (,) ji hev veqetîne. Vala bihêle da ku hemû ziman werin nîşandan.",
helpInline: true
}),
new OO.ui.FieldLayout(this.onlyOnClickCheckbox, {
label: "Tenê veke wextê \"+Ziman/beşeke din\" were tikandin",
align: "inline",
help: "Eger were hilbijartin, ev amûr êdî otomatîk li ser rûpelên nû dest pê nake — divê meriv her carê pêl \"+Ziman/beşeke din\" bike ku bişixule.",
helpInline: true
}),
new OO.ui.FieldLayout(this.portletDropdown, {
label: "Cihê lînka \"+Ziman/beşeke din\"",
align: "top",
help: "\"Otomatîk\" li gorî rûyê wîkiyê hildibijêre.",
helpInline: true
}),
new OO.ui.FieldLayout(this.quietCheckbox, {
label: "Moda bêdeng (kêmtir agahdarî nîşan bide)",
align: "inline",
help: "Agahdariyên normal nîşan nede. Xeta her tim tên xuyakirin.",
helpInline: true
})
]);
this.$body.addClass("wiktku-prefs-dialog-body").append(fieldset.$element);
};
WiktkuPrefsDialog.prototype.getSetupProcess = function (data) {
return WiktkuPrefsDialog.super.prototype.getSetupProcess.call(this, data)
.next(function () {
this.langCodesInput.setValue(wiktkuPrefs.langCodes.join(", "));
this.onlyOnClickCheckbox.setSelected(!!wiktkuPrefs.onlyOnClick);
this.portletDropdown.setValue(wiktkuPrefs.portlet || "auto");
this.quietCheckbox.setSelected(!!wiktkuPrefs.quiet);
}, this);
};
WiktkuPrefsDialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === "save") {
return new OO.ui.Process(function () {
var codes = dialog.langCodesInput.getValue()
.split(/[,\s]+/)
.map(function (c) { return c.trim().toLowerCase(); })
.filter(function (c) { return !!c; });
wiktkuPrefs = {
langCodes: codes,
onlyOnClick: dialog.onlyOnClickCheckbox.isSelected(),
portlet: dialog.portletDropdown.getValue() || "auto",
quiet: dialog.quietCheckbox.isSelected()
};
wiktkuSavePrefs(wiktkuPrefs);
mw.notify(
"Tercîh hatin qeydkirin. Rûpelê ji nû ve bar bike (F5).",
{ type: "success", tag: "wiktku-prefs-save" }
);
dialog.close({ action: "save" });
});
}
if (action === "cancel") {
return new OO.ui.Process(function () {
dialog.close({ action: "cancel" });
});
}
if (action === "clearcache") {
return new OO.ui.Process(function () {
if (!window.confirm(
"Tu dixwazî hemû cache-ya vê amûrê ji her rûpelê paqij bikî " +
"(danûstandinên qeydkirî yên hemû rûpelan, bilindahiyên qutiyan)? " +
"Tercîhên li jor nayên jêbirin. Ev tişt nayê vegerandin."
)) return;
wiktkuClearAllScriptCache();
mw.notify("Hemû cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear-all" });
dialog.close({ action: "clearcache" });
});
}
return WiktkuPrefsDialog.super.prototype.getActionProcess.call(this, action);
};
wiktkuPrefsWindowManager = new OO.ui.WindowManager();
$(document.body).append(wiktkuPrefsWindowManager.$element);
wiktkuPrefsDialog = new WiktkuPrefsDialog();
wiktkuPrefsWindowManager.addWindows([wiktkuPrefsDialog]);
}
wiktkuPrefsWindowManager.openWindow(wiktkuPrefsDialog);
});
}
var targetContainer;
var skin = mw.config.get('skin');
if (wiktkuPrefs.portlet && wiktkuPrefs.portlet !== "auto") {
targetContainer = wiktkuPrefs.portlet;
} else if (skin === 'vector-2022') {
targetContainer = 'p-cactions';
} else if (skin === 'vector') {
targetContainer = 'p-tb';
} else {
targetContainer = 'p-views';
}
mw.util.addPortletLink(targetContainer,
mw.util.getUrl(mw.config.get("wgPageName"), { action: "submit", wiktkuaddlang: "1" }),
"+Ziman/beşeke din", "ca-wiktku-addlang",
"Li vê rûpelê, ziman an jî beşeke nû zêde bike");
// If "onlyOnClick" is set, the wizard should ONLY ever open via the
// "+Ziman/beşeke din" link — never automatically
var wiktkuNewPageDetected = $(".mw-newarticletext, .mw-newarticletextanon").length > 0;
if (wiktkuPrefs.onlyOnClick ? !addLangMode : (!addLangMode && !wiktkuNewPageDetected)) {
return;
}
// ── Shared helpers ───────────────────────────────────────────────────
// Generic text/UI utilities live in their own package file now — see the
// header of util.js for why. Aliased to bare names here so the ~400 call
// sites below read exactly as they did when these were local functions.
var wiktkuUtil = require("./start/util.js");
var capitalize = wiktkuUtil.capitalize;
var decapitalize = wiktkuUtil.decapitalize;
var escapeRegExp = wiktkuUtil.escapeRegExp;
var stripWikilinks = wiktkuUtil.stripWikilinks;
var splitTercimeIzah = wiktkuUtil.splitTercimeIzah;
var insertAtCursor = wiktkuUtil.insertAtCursor;
var wrapNativeSelect = wiktkuUtil.wrapNativeSelect;
var initSelects = wiktkuUtil.initSelects;
var wiktkuInitManualResize = wiktkuUtil.initManualResize;
// Inserts a line-style template snippet ("* {{…}}") at the cursor,
// prefixing it with a newline unless it's going at the very start of the
// box or right after one already — otherwise picking a second template
// from the pron dropdown right after the first lands them both on the
// same line: "* {{ku-IPA}}* {{ku-kîte}}".
function insertLineSnippet(el, snippet) {
var before = el.value.slice(0, el.selectionStart);
if (before && !/\n$/.test(before)) snippet = "\n" + snippet;
insertAtCursor(el, snippet);
}
function langHeaderSource(codePattern) {
return "\n?==\\s*\\{\\{\\s*ziman\\s*\\|\\s*(" + codePattern + ")\\s*\\}\\}\\s*==";
}
// Delete button (real OOUI "trash" icon, destructive-flagged) shared by
// the Mane tab bar and the Beş tab bar. One button per bar — sitting
// right before "+ Maneyeke din" / "+ Beşeke din", single button acting on
// the one you're looking at, so it only ever has one thing it can delete.
// "oojs-ui.styles.icons-moderation" carries the trash glyph; it's not in
// the wizard's main startup dependency list since most people never see
// a delete button (needs 2+ Mane or Beş), so we only fetch it here, the
// first time one is actually built — the icon just appears once the CSS
// arrives, no need to wait for it before constructing the button.
function makeTrashButton(title) {
mw.loader.using("oojs-ui.styles.icons-moderation");
return new OO.ui.ButtonWidget({
icon: "trash",
flags: ["destructive"],
framed: false,
invisibleLabel: true,
label: title,
title: title,
classes: ["wiktku-trash-btn"]
});
}
function findLangHeader(text, code) {
var m = new RegExp(langHeaderSource(escapeRegExp(code))).exec(text);
return m ? { index: m.index, match: m[0] } : null;
}
var catSrcTemplates = null;
var catSrcTemplatesPromise = null;
function fetchCatSrcTemplates() {
if (catSrcTemplatesPromise) return catSrcTemplatesPromise;
catSrcTemplatesPromise = new mw.Api().get({
action: "query",
list: "categorymembers",
cmtitle: "Kategorî:Çavkanî",
cmnamespace: 10,
cmlimit: "max",
formatversion: 2
}).then(function (data) {
var members = (data.query && data.query.categorymembers) || [];
catSrcTemplates = $.map(members, function (m) {
// Pêşgira navî ya "Şablon:" rake.
return m.title.replace(/^[^:]+:/, "");
});
catSrcTemplates.sort(function (a, b) { return a.localeCompare(b, userLang); });
return catSrcTemplates;
}, function () {
catSrcTemplates = [];
return catSrcTemplates;
});
return catSrcTemplatesPromise;
}
var pageName = mw.config.get("wgPageName").replaceAll(/_/g, " ");
if (ns) pageName = pageName.replace(/.*\//, "");
var pageNameRe = escapeRegExp(pageName);
var userLang = mw.config.get("wgUserLanguage");
var formCache = new ObjectStorage("wiktku_start_draft_" + mw.config.get("wgPageName"), 1);
var langsByCode = {};
// Looks up the display label for a language code, falling back to the
// code itself if it isn't known yet (e.g. langsByCode not loaded).
function getLangLabel(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
// Populated from [[MediaWiki:Gadget-start.js/data.json]] at runtime.
var metadata = {};
var ZARAVA_LANG_CODES = ["ckb", "sdh", "lki", "hac", "zza"];
// Converts a {code: label} object (langChoices/posChoices in data.json)
// into the {code, label[, sortKey]} array the rest of the script uses.
// isLang: true adds a sortKey and shows "(code)" after the label —
// language choices only.
function choicesFromObject(obj, isLang) {
var result = [];
$.each(obj || {}, function (code, label) {
var choice = { code: code, label: label };
if (isLang) choice.sortKey = label;
result.push(choice);
});
return result;
}
function getZaravaKeys(choices) {
var byCode = {};
$.each(choices || [], function (_, c) { byCode[c.code] = c; });
var result = [];
$.each(ZARAVA_LANG_CODES, function (_, code) {
if (byCode[code]) result.push(byCode[code]);
});
return result;
}
function needsTransliteration(langCode) {
var meta = metadata[langCode];
if (!meta || !meta.sc) return false;
var scripts = Array.isArray(meta.sc) ? meta.sc : [meta.sc];
return scripts.length > 0 && scripts[0] !== "Latn";
}
// metadata[lang].sc is either a single script string or an array of
// several — only the latter means the language is actually written in
// more than one script.
function langHasMultipleScripts(langCode) {
var meta = metadata[langCode];
return !!(meta && Array.isArray(meta.sc) && meta.sc.length > 1);
}
// Kodên cinsiyetê yên di metadata.g de — ji data.json tê dagirtin
var GENDER_BASE_LABELS = {};
// Number codes (y, d, p, p+, tp, j, j+, j/nj, nj, nj+, hj!, hj?) from
// data.json's "numberLabels"
var NUMBER_LABELS = {};
// Distinguishes the Jimar dropdown (j= param) from the Cîns dropdown
// (c= param): a code that's a key in NUMBER_LABELS is a number code,
// everything else is a gender code. Compound codes like "m-p"/"n-p" are
// gender+plural pairs and correctly stay on the gender side.
function isNumberCode(code) {
return Object.prototype.hasOwnProperty.call(NUMBER_LABELS, code);
}
// Builds the Jimar dropdown's <option> text for one code: "j — jimarbar".
function numberOptionLabel(code) {
var def = NUMBER_LABELS[code];
if (!def) return code;
var text = def.display;
return text ? (code + " — " + text) : code;
}
// Sub-lists that only make sense for a plain (non-şablon) sense — hidden
// when a şablon (e.g. {{guharto|…}}) is chosen for that sense
var HIDDEN_ON_TPL_GROUPS = [
"wiktku-dfn-alternativ",
"wiktku-dfn-hevmane",
"wiktku-dfn-dijmane",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
];
function genderLabel(code) {
if (code === "p") return "Pirjimar";
var pluralMatch = code.match(/^(.+)-p$/);
if (pluralMatch && GENDER_BASE_LABELS[pluralMatch[1]]) {
return GENDER_BASE_LABELS[pluralMatch[1]] + " (pirjimar)";
}
return GENDER_BASE_LABELS[code] || code;
}
// ── Template explanations ────────────────────────────────────────────
// Every place a template can be picked shows the same row: a status
// dot, the name, a link to the template page, and an ⓘ. This is the ⓘ.
//
// Text comes from the template's own TemplateData where it has any, so
// the explanation stays right without anyone maintaining a second copy
// by hand — with the desc already in data.json as the fallback for
// templates that have none.
var wiktkuTplDataCache = {}; // name -> {desc, params[]} or null when absent
/** Fetches TemplateData for one template. Resolves to null if the
* template has none (a plain, undocumented template — very common). */
function fetchTemplateData(name) {
if (Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, name)) {
return $.Deferred().resolve(wiktkuTplDataCache[name]).promise();
}
return new mw.Api().get({
action: "templatedata",
titles: "Şablon:" + name,
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
var first = null;
$.each(pages, function (_, p) { if (!first) first = p; });
var out = null;
if (first) {
var desc = first.description;
// TemplateData descriptions are language maps when the
// template is documented in more than one language.
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(first.params || {}, function (key, p) {
var label = p && p.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(p && p.required) });
});
out = { desc: desc || "", params: params };
}
wiktkuTplDataCache[name] = out;
return out;
}, function () {
// No TemplateData, or the API is unhappy — cache the miss so a
// repeated ⓘ click doesn't re-request it every time.
wiktkuTplDataCache[name] = null;
return null;
});
}
/** Fetches TemplateData for many templates in one request and fills the
* cache; resolves either way, a template with no TemplateData caches as null. */
function prefetchTemplateData(names) {
var missing = $.grep(names || [], function (n) {
return n && !Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n);
});
if (!missing.length) return $.Deferred().resolve().promise();
// The API caps a titles list at 50 for normal users.
missing = missing.slice(0, 50);
return new mw.Api().get({
action: "templatedata",
titles: $.map(missing, function (n) { return "Şablon:" + n; }).join("|"),
redirects: 1,
formatversion: 2
}).then(function (data) {
var pages = (data && data.pages) || {};
$.each(pages, function (_, page) {
if (!page || !page.title) return;
var name = String(page.title).replace(/^[^:]+:/, "");
var desc = page.description;
if (desc && typeof desc === "object") desc = desc[userLang] || desc.en || desc[Object.keys(desc)[0]];
var params = [];
$.each(page.params || {}, function (key, prm) {
var label = prm && prm.label;
if (label && typeof label === "object") label = label[userLang] || label.en || label[Object.keys(label)[0]];
params.push({ name: key, label: label || "", required: !!(prm && prm.required) });
});
wiktkuTplDataCache[name] = { desc: desc || "", params: params };
});
// Anything the API didn't answer for simply has none — cache the
// miss so the per-row ⓘ doesn't re-request it one at a time.
$.each(missing, function (_, n) {
if (!Object.prototype.hasOwnProperty.call(wiktkuTplDataCache, n)) wiktkuTplDataCache[n] = null;
});
}, function () {
$.each(missing, function (_, n) { wiktkuTplDataCache[n] = null; });
});
}
/** Rewrites each tagged <option> in `sel` as "name — description", using
* TemplateData. Only fills rows that have no description yet, so the
* hand-written ones in data.json (which carry usage examples the API
* does not) are left exactly as they are. */
function applyTplDescriptions(sel) {
if (!sel || !sel.length) return;
var changed = false;
sel.find("option[data-tpl]").each(function () {
var opt = $(this);
var name = opt.attr("data-tpl");
if (opt.text().indexOf(" — ") !== -1) return; // already described
var td = wiktkuTplDataCache[name];
var desc = (td && td.desc) || localTplDesc(name);
if (!desc) return;
// One line: TemplateData descriptions can run to a paragraph.
desc = $.trim(String(desc).split("\n")[0]);
if (desc.length > 90) desc = desc.slice(0, 90) + "…";
opt.text(name + " — " + desc);
changed = true;
});
// The picker caches option text for searching, so it has to re-read.
if (changed) sel.trigger("wiktku:refresh");
}
/** Hand-written fallback description, from data.json.
*
* This is the escape hatch for templates with no TemplateData on the
* wiki: add a "desc" to the entry in data.json and it shows up in the
* dropdown and in the ⓘ immediately, no code change. TemplateData is
* still the better place — VisualEditor and every other tool reads it
* too — but this keeps the picker useful in the meantime.
*
* Works for any pool: pronTpls, etymTpls, tplDefs, jederDefs. */
function localTplDesc(name) {
var pools = [steps.pron.pronTpls, steps.etym.etymTpls, steps.dfn.tplDefs, steps.dfn.jederDefs];
var found = "";
$.each(pools, function (_, pool) {
if (pool && pool[name] && pool[name].desc) { found = pool[name].desc; return false; }
});
return found;
}
/** Opens the inline explanation panel for `name`, anchored under the
* picker it was opened from. */
function showTemplateInfo(name, anchorWrap) {
$(".wiktku-tpl-info").remove();
var panel = $("<div class='wiktku-tpl-info'></div>");
panel.append($("<div class='wiktku-tpl-info-name'></div>").text("{{" + name + "}}"));
var body = $("<div class='wiktku-tpl-info-desc'></div>").text("Tê barkirin…");
panel.append(body);
var params = $("<div class='wiktku-tpl-info-params'></div>");
panel.append(params);
panel.append($("<a class='wiktku-tpl-info-link' target='_blank' rel='noopener'></a>")
.attr("href", wiktkuUtil.tplPageUrl(name))
.text("Rûpela şablonê veke →"));
var close = $("<button type='button' class='wiktku-tpl-info-close' title='Bigire'>✕</button>");
close.on("click", function () { panel.remove(); });
panel.prepend(close);
$(anchorWrap).after(panel);
fetchTemplateData(name).then(function (td) {
var desc = (td && td.desc) || localTplDesc(name);
body.text(desc || "Ji bo vê şablonê îzahek nehatiye nivîsandin.");
if (td && td.params && td.params.length) {
params.append($("<div class='wiktku-tpl-info-params-title'></div>").text("Parametre:"));
var listEl = $("<ul></ul>");
$.each(td.params, function (_, p) {
var line = p.name + (p.label ? " — " + p.label : "") + (p.required ? " (mecbûrî)" : "");
listEl.append($("<li></li>").text(line));
});
params.append(listEl);
}
});
}
/** Marks a picker's <option>s as template rows so the searchable widget
* renders them with the dot / 🔗 / ⓘ affordances, and wires the ⓘ
* handler for that picker. `nameOf` maps an <option> to a template
* name — the pickers store different things in value= (a snippet for
* the insert dropdowns, the bare name for the sense selector). */
function markTemplateOptions(sel, nameOf) {
if (!sel || !sel.length) return;
var names = [];
sel.find("option").each(function () {
var opt = $(this);
var name = nameOf(opt);
if (!name) return;
opt.attr("data-tpl", name);
// Deprecated/typo templates are the "special cases only" ones.
opt.attr("data-tpl-status", /kevnbûyî|şaşnivîs|kêmpeyda/i.test(opt.text()) ? "yellow" : "green");
names.push(name);
});
sel.data("wiktkuTplInfo", showTemplateInfo);
// Fill in the descriptions. Matters most for the Mane picker, whose
// entries in data.json are names with no desc at all — "rastnivîs",
// "ku-Latn", "guharto" on their own. One request covers every
// template in the picker, cached for the per-row ⓘ as well.
prefetchTemplateData(names).always(function () {
applyTplDescriptions(sel);
});
}
/** Pulls the template name out of a snippet like "* {{ku-IPA}}" or
* "{{ji|SOURCELANG|CODE|…}}". */
function tplNameFromSnippet(snippet) {
var m = /\{\{\s*([^|}\n]+)/.exec(snippet || "");
return m ? $.trim(m[1]) : "";
}
// True if a sense has content beyond mane worth keeping (etîket, word
// lists, nimûne, jêder) — so it's still emitted with {{mane?}} if empty.
function senseHasExtraContent(s) {
if (s.etiket && $.grep(s.etiket, function (e) { return $.trim(e) !== ""; }).length) return true;
if (s.kategori && $.grep(s.kategori, function (k) { return $.trim(k) !== ""; }).length) return true;
if (s.alternativ.length || s.hevmane.length || s.dijmane.length ||
s.koordine.length || s.jornav.length || s.jernav.length || (s.dever && s.dever.length)) return true;
if (s.nimune && $.grep(s.nimune, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0) return true;
if (senseHasJeder(s)) return true;
return false;
}
// A technically-"complete" entry (Ziman + Cureyê rêzimanî filled) with
// nothing in Mane, Bi zaravayên din or Werger is still meaningfully
// empty, so this is checked separately at submit-click time with its
// own message.
function hasMinRequiredContent(values) {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasMane = dfnSenses.some(function (s) {
return (s.tpl && s.tplTitle) || (!s.tpl && (s.mane !== "" || senseHasExtraContent(s)));
});
if (hasMane) return true;
if (values.lang === "ku") {
var hasWerger = false;
$("#wiktku-start-trans .wiktku-trans-group").each(function () {
if (steps.trans.getGroupTranslations($(this)).length) hasWerger = true;
});
if (hasWerger) return true;
var hasZarava = false;
$("#wiktku-start-zarava .wiktku-zarava-group").each(function () {
if (steps.zarava.getGroupTranslations($(this)).length) hasZarava = true;
});
if (hasZarava) return true;
}
return false;
}
// Bolds the *first* occurrence of the page name
function boldPageNameOnce(text) {
if (!text || !pageNameRe) return text;
var re = new RegExp("(" + pageNameRe + "([^( |,|;|\\?|\\.|\\!)]+)?)", "i");
return text.replace(re, "'''$1'''");
}
// Converts literal line breaks typed into a nimûne/jêgirtin box into
// wikitext <br> tags,
function nlToBr(text) {
return text ? text.replace(/\r\n|\r|\n/g, "<br>") : text;
}
function escapeHtmlForBackdrop(str) {
return String(str)
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">");
}
// Mirrors a textarea's text, with a small highlighted marker placed
// right after each line that's followed by an actual typed/pasted
// linebreak. Rendered into a transparent backdrop layer behind the
// textarea (see ensureLinebreakBackdrop()), so only the point of the
// break is marked instead of the whole box.
function buildLinebreakBackdropHtml(text) {
var lines = text.split(/\r\n|\r|\n/);
return $.map(lines, function (line, i) {
var isLast = i === lines.length - 1;
return escapeHtmlForBackdrop(line) +
(isLast ? "" : "<mark class='wiktku-linebreak-mark'>↵</mark>\n");
}).join("");
}
// Copies the metrics that affect where text wraps/lands (font, line
// height, padding, border) from the real textarea onto its backdrop,
// via computed style rather than shared CSS classes — most of the
// box's own rules are written as "textarea.foo", which a <div> with
// the same class wouldn't match. Re-run on every update in case a
// responsive breakpoint changed the textarea's own font-size.
function syncLinebreakBackdropMetrics(textarea, backdrop) {
var cs = window.getComputedStyle(textarea[0]);
backdrop.css({
"box-sizing": cs.boxSizing,
"padding-top": cs.paddingTop,
"padding-right": cs.paddingRight,
"padding-bottom": cs.paddingBottom,
"padding-left": cs.paddingLeft,
"border-width": cs.borderWidth,
"border-style": "solid",
"font-family": cs.fontFamily,
"font-size": cs.fontSize,
"font-weight": cs.fontWeight,
"line-height": cs.lineHeight,
"letter-spacing": cs.letterSpacing
});
}
// Lazily wraps a textarea in its own positioning anchor (independent
// of .wiktku-resize-wrap, so this doesn't depend on load order versus
// wiktkuMakeManualResizable) and inserts the transparent backdrop
// layer used to mark the exact point of a linebreak. Safe to call
// more than once on the same textarea — the backdrop is cached.
function ensureLinebreakBackdrop(textarea) {
var existing = textarea.data("wiktkuLinebreakBackdrop");
if (existing) return existing;
textarea.wrap("<div class='wiktku-linebreak-wrap'></div>");
var backdrop = $("<div aria-hidden='true'></div>").addClass("wiktku-linebreak-backdrop");
textarea.before(backdrop);
textarea.data("wiktkuLinebreakBackdrop", backdrop);
textarea.on("scroll.wiktkuLinebreak", function () {
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
});
return backdrop;
}
// Flags a nimûne/jêgirtin textarea (via CSS class, for the border
// tint + tooltip) while it contains a typed/pasted linebreak, so the
// user notices before it's silently turned into <br> on output.
// Marks only the exact point of the break via a mirrored backdrop,
// rather than tinting the whole box.
function highlightLinebreak(el) {
var textarea = $(el);
var text = textarea.val();
var hasBreak = /\r|\n/.test(text);
textarea
.toggleClass("wiktku-has-linebreak", hasBreak)
.attr("title", hasBreak ? "Rêza nû dê wek <br> were nivîsandin" : "");
if (!hasBreak) {
var cached = textarea.data("wiktkuLinebreakBackdrop");
if (cached) cached.hide();
textarea.removeClass("wiktku-has-linebreak-source");
return;
}
var backdrop = ensureLinebreakBackdrop(textarea);
textarea.addClass("wiktku-has-linebreak-source");
syncLinebreakBackdropMetrics(textarea, backdrop);
backdrop.html(buildLinebreakBackdropHtml(text)).show();
backdrop.scrollTop(textarea.scrollTop()).scrollLeft(textarea.scrollLeft());
}
// Each nimûne item can be { text, tr, t }: text, transliteration, translation.
function formatNimune(nimunes, lang, hasJeder) {
var hasNimune = nimunes && $.grep(nimunes, function (n) {
return (typeof n === "object" ? n.text : n) !== "";
}).length > 0;
if (!hasNimune) {
return hasJeder ? "" : "\n#* {{nimûne?|" + lang + "}}";
}
return $.map(nimunes, function (n) {
var text = typeof n === "object" ? n.text : n;
if (!text) return undefined;
var bolded = nlToBr(boldPageNameOnce(text));
var tpl = "\n#: {{nimûne|" + lang + "|" + bolded;
if (typeof n === "object") {
if (n.tr) tpl += "|tr=" + nlToBr(n.tr);
// Translation as third positional param (no key needed).
if (n.t) tpl += "|" + nlToBr(n.t);
}
// Short examples fit on one line together with their translation.
// rêz=1 is not used for ku — definitions are shown on separate lines there.
if (text.length < 60 && lang !== "ku") tpl += "|rêz=1";
tpl += "}}";
return tpl;
}).join("");
}
// Formats jêder (citation) entries into "#* {{jêder…|…}}" lines; skips
// entries with no quote text. Falls back to the generic {{jêder}}
// template when no specific template was chosen.
function formatJeder(jederList, lang) {
if (!jederList || !jederList.length) return "";
return $.map(jederList, function (jeder) {
if (!jeder || !jeder.quote) return undefined;
var quote = nlToBr(boldPageNameOnce(jeder.quote));
var quoteTr = nlToBr(jeder.quoteTr);
var quoteT = nlToBr(jeder.quoteT);
if (jeder.jederTpl) {
var cite = "\n#* {{" + jeder.jederTpl + "|" + lang;
cite += "|jêgirtin=" + quote;
if (quoteTr) cite += "|tr=" + quoteTr;
if (quoteT) cite += "|tercime=" + quoteT;
$.each(jeder.jêderFields || {}, function (key, val) {
if (val) cite += "|" + key + "=" + val;
});
cite += "}}";
return cite;
}
var out = "\n#* {{jêder|" + lang + "|jêgirtin=" + quote;
if (quoteTr) out += "|tr=" + quoteTr;
if (quoteT) out += "|tercime=" + quoteT;
// A şablon picked from Kategorî:Çavkanî is transcluded as the
// source param (e.g. |{{Kovara X}}); manual text is used as-is.
var srcParam = jeder.jederCatTpl ? ("{{" + jeder.jederCatTpl + "}}") :
(jeder.jederManual ? jeder.jederManual : "çavkanî?");
// A positional param containing "=" gets parsed as its own key=value
// pair by MediaWiki and breaks the template, so force it to be
// explicitly named (|2=) whenever that's the case.
out += (srcParam.indexOf("=") !== -1 ? "|2=" : "|") + srcParam;
out += "}}";
return out;
}).join("");
}
// True if a sense has at least one jêder entry with quote text.
function senseHasJeder(sense) {
return !!(sense.jeder && $.grep(sense.jeder, function (j) {
return j && j.quote;
}).length > 0);
}
// Builds "{{tplName|lang|part1|part2|…}}" from a sense field (kategori,
// etiket), wrapped inline (" {{…}} ") or on its own line ("\n#: {{…}}").
function buildSenseTpl(sense, field, lang, tplName) {
if (!sense[field] || !sense[field].length) return "";
var parts = sense[field].map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
var call = "{{" + tplName + "|" + lang + "|" + parts.join("|") + "}}";
return call
}
// Dever/Şîve values only ever get their own {{dever|…}} call for ku —
// no |lang| param, since the template is Kurdish-dialect-specific. For
// every other language there's no dever template at all; see
// buildEtiketTpl() below, which folds them into {{e|}} instead.
function buildDeverTpl(sense, lang) {
if (lang !== "ku" || !sense.dever || !sense.dever.length) return "";
var parts = sense.dever.map(function (p) { return $.trim(p); })
.filter(function (p) { return p !== ""; });
if (!parts.length) return "";
return "{{dever|" + parts.join("|") + "}}";
}
// Builds the {{e|lang|…}} etîket call. For non-ku senses, Dever/Şîve
// values are appended after the regular etîket values instead of going
// into their own {{dever|}} call (see buildDeverTpl() above).
function buildEtiketTpl(sense, lang) {
var etiket = (sense.etiket || []).slice();
if (lang !== "ku" && sense.dever && sense.dever.length) {
etiket = etiket.concat(sense.dever);
}
return buildSenseTpl({ etiket: etiket }, "etiket", lang, "e");
}
/**
* Assembles the nimûne + jêder wikitext block for a sense. Shared by
* both the template-sense and normal-mane-sense branches below.
*/
function buildCite(sense, lang) {
return formatNimune(sense.nimune, lang, senseHasJeder(sense)) +
formatJeder(sense.jeder, lang);
}
// ── Inline modifier system ───────────────────────────────────────────────
// Quick-pick suggestions for the Dever/Şîve chip picker on each sense
// (see buildSenseBlock's deverWrap). Free text is always accepted too —
// this list only saves typing for the common regional names.
var DEVER_SUGGESTIONS = [
"Behdînan", "Serhed", "Botan", "Hekarî", "Dêrsim",
"Mêrdîn", "Colemêrg", "Efrîn", "Dihok", "Qamişlo", "Kobanî"
];
// Appends a ⊕ button to a word input <li>. Clicking it shows a compact
// modifier picker; selecting one prompts for the value and appends
// <modifier:value> to the textarea's current content.
var WORD_MODIFIERS = [
{ key: "t", label: "t: tercime / îzah", hint: "Tercimeya kelîmeyê an îzaha wê" },
{ key: "cuda", label: "cuda: alt display", hint: "Forma nîşankirinê ya alternatîv" },
{ key: "c", label: "c: cinsiyet", hint: "Cinsiyeta kelîmeyê" },
{ key: "e", label: "e: etîket (li pêşiyê)", hint: "Etîketek li pêşiya kelîmeyê" },
{ key: "ee", label: "ee: etîket (li dawiyê)", hint: "Etîketek li dawiya kelîmeyê" },
{ key: "q", label: "q: wesf (li pêşiyê)", hint: "Wesf li pêşiya kelîmeyê" },
{ key: "qq", label: "qq: wesf (li dawiyê)", hint: "Wesf li dawiya kelîmeyê" },
{ key: "crz", label: "crz: cureyê rêzimanî", hint: "Cureyê rêzimanî yê kelîmeyê" },
{ key: "hrf", label: "hrf: herfiyen", hint: "Herfiyen, manaya rasterast" },
{ key: "ref", label: "ref: çavkanî", hint: "Çavkanî" }
];
// Short parenthetical prefix each modifier key gets in a pill's friendly
// display — e.g. "peyv<t:wate>" reads as "peyv (wate)", "peyv<c:mê>" as
// "peyv (cinsiyet: mê)". Keyed the same as WORD_MODIFIERS.
var WORD_MODIFIER_DISPLAY_PREFIX = {
t: "tercime: ",
cuda: "cuda: ",
c: "cinsiyet: ",
e: "etîket: ",
ee: "etîket: ",
q: "wesf: ",
qq: "wesf: ",
crz: "cureyê rêzimanî: ",
hrf: "herfiyen: ",
ref: "çavkanî: "
};
/** Splits a raw "word<t:val1><c:val2>" field value into { word, mods }
* — mods is an array of {key, value} in the order they appear. Used to
* re-hydrate a word box's UI (plain word + separate modifier chips)
* from a value that was typed/restored in the combined raw form. */
function parseWordValue(raw) {
var m = /^([\s\S]*?)((?:<[a-zA-Z]+:[^<>]*>)+)$/.exec(raw || "");
if (!m) return { word: raw || "", mods: [] };
var mods = [];
var re = /<([a-zA-Z]+):([^<>]*)>/g, mm;
while ((mm = re.exec(m[2])) !== null) mods.push({ key: mm[1], value: mm[2] });
return { word: m[1], mods: mods };
}
/** Re-joins { word, mods } back into the raw "word<t:val1><c:val2>" form
* that readList()/output actually read. */
function joinWordValue(word, mods) {
if (!word) return "";
var out = word;
$.each(mods, function (_, mo) { out += "<" + mo.key + ":" + mo.value + ">"; });
return out;
}
/** Short "(key: value)" chip label for one attached modifier. */
function modChipLabel(mo) {
var prefix = WORD_MODIFIER_DISPLAY_PREFIX[mo.key];
return (prefix === undefined ? mo.key + ": " : prefix) + mo.value;
}
// Sub-groups (inside a sense/definition block) that get the same ⊕
// modifier button as makeWordListStep fields.
var MODIFIER_SUBGROUPS = {
"wiktku-dfn-alternativ": true,
"wiktku-dfn-hevmane": true,
"wiktku-dfn-dijmane": true,
"wiktku-dfn-koordine": true,
"wiktku-dfn-jornav": true,
"wiktku-dfn-jernav": true
};
/**
* Wraps a freshly-created textarea in a <li> with a ⊕ modifier button.
* `ta` stays the hidden "source of truth" element — its value is always
* the raw "word<key:value>…" text that readList()/restoreSubList()/
* output already read and write, and the caller's own growth listener
* (onSubInput/refreshForTextField, bound to `ta`'s "input" event before
* this runs) keeps working untouched. Everything the person actually
* sees and types into is separate: a plain word box (wordInput, holds
* only the word — modifier tags can never be typed/corrupted through
* it, and it never changes shape, so it always looks and behaves like
* any other text field) plus a row of small tag pills, one per attached
* modifier, attached right above that word's box. Clicking a tag pill
* (not its ×) reopens it for editing its value; the ⊕ button adds more.
* Returns the <li>.
*/
function makeModifierLi(ta) {
var li = $("<li class='wiktku-word-li'></li>");
var narrow = (ta.attr("class") || "").split(/\s+/).indexOf("wiktku-start-narrow") !== -1;
// Deliberately NOT ta's own classes (wiktku-dfn-subfield/wiktku-
// start-step-field) — a lot of code elsewhere counts/indexes
// elements by those exact classes (restoreMultiField, readList's
// siblings, refresh()'s per-step field lookups, …); a second
// visible textarea matching them would throw those counts off.
// wiktku-word-input picks up the same look via styles.css instead.
var wordInput = $("<textarea></textarea>")
.attr("rows", ta.attr("rows") || 1)
.addClass("wiktku-word-input" + (narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", ta.attr("placeholder") || "");
ta.hide();
// Placeholders on the (hidden) canonical field are set by shared
// code like updateSubPlaceholders() that has no reason to know
// wordInput exists — mirror them across automatically instead of
// touching every one of those call sites.
if (window.MutationObserver) {
new MutationObserver(function () {
wordInput.attr("placeholder", ta.attr("placeholder") || "");
}).observe(ta[0], { attributes: true, attributeFilter: ["placeholder"] });
}
var modsRow = $("<div class='wiktku-word-mods'></div>").hide();
var btn = $("<button type='button' class='wiktku-mod-btn' title='Modifiyerê lê zêde bike'>⊕</button>");
var removeBtn = $("<button type='button' class='wiktku-word-remove' title='Vê kelîmeyê jê bibe'>×</button>").hide();
var picker = $("<div class='wiktku-mod-picker'></div>").hide();
var boxRow = $("<div class='wiktku-word-box-row'></div>")
.append(wordInput).append(btn).append(removeBtn);
// The word text and its modifiers are tracked separately — mods is
// never part of what wordInput holds, so typing into the word box
// can only ever change the word, never a tag. Parsed once from
// whatever ta already holds (e.g. a value restored from cache).
var parsed = parseWordValue(ta.val());
wordInput.val(parsed.word);
var mods = parsed.mods;
function usedKeys() {
var used = {};
$.each(mods, function (_, mo) { used[mo.key] = true; });
return used;
}
/** Writes ta's value from the current word+mods and tells the
* caller's growth logic (bound to ta's "input" event) about it. */
function syncTa() {
ta.val(joinWordValue($.trim(wordInput.val()), mods));
ta.trigger("input");
}
/** Opens the same value-entry UI buildPills() uses for a NEW
* modifier, pre-filled, so an existing tag's value can be changed —
* used both for "add" (existingIdx === -1) and "edit" (click on an
* existing tag pill). */
function openValueEditor(mod, existingIdx) {
picker.hide();
var valWrap = $("<span class='wiktku-mod-val-wrap'></span>");
var valInp = $("<input type='text' class='wiktku-mod-val-input'>")
.attr("placeholder", mod.hint + "…")
.attr("size", 20)
.val(existingIdx === -1 ? "" : mods[existingIdx].value);
var confirm = $("<button type='button' class='wiktku-mod-confirm'>✓</button>");
var cancel = $("<button type='button' class='wiktku-mod-cancel'>✕</button>");
function apply() {
var v = $.trim(valInp.val());
if (v) {
if (existingIdx === -1) mods.push({ key: mod.key, value: v });
else mods[existingIdx].value = v;
syncTa();
renderMods();
}
valWrap.remove();
wordInput.focus();
}
confirm.on("click", apply);
cancel.on("click", function () { valWrap.remove(); wordInput.focus(); });
valInp.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); apply(); }
if (e.which === 27) { valWrap.remove(); wordInput.focus(); }
});
valWrap.append(valInp).append(confirm).append(cancel);
li.append(valWrap);
valInp.focus();
}
function modDef(key) {
var found = null;
$.each(WORD_MODIFIERS, function (_, wm) { if (wm.key === key) found = wm; });
return found;
}
function renderMods() {
modsRow.empty();
$.each(mods, function (idx, mo) {
var chip = $("<span class='wiktku-word-mod-chip' role='button' tabindex='0'></span>").text(modChipLabel(mo));
var x = $("<button type='button' class='wiktku-word-mod-chip-remove' title='Vê xisûsiyetê jê bibe'>×</button>");
x.on("click", function (e) {
e.stopPropagation();
mods.splice(idx, 1);
syncTa();
renderMods();
});
// Click the chip itself (not its ×) to change the value —
// the tag (which modifier this is) can't be changed this
// way, only removed and re-added via ⊕, same as a word's
// text can only be edited, never its tags.
chip.on("click", function () {
var def = modDef(mo.key);
if (def) openValueEditor(def, idx);
});
chip.append(x);
modsRow.append(chip);
});
modsRow.toggle(!!mods.length);
}
renderMods();
// The 10 pills (each its own click handler) are built lazily, on
// first open, rather than for every word field up front — this runs
// once per makeWordListStep/sub-list field, per sense, and most
// fields never touch their ⊕ picker at all. Building it eagerly was
// a big share of why switching Beş tabs was slow: restoreDfn()
// rebuilds every sense's fields from scratch on every switch.
var pillsBuilt = false;
function buildPills() {
if (pillsBuilt) return;
pillsBuilt = true;
$.each(WORD_MODIFIERS, function (_, mod) {
var pill = $("<button type='button' class='wiktku-mod-pill'></button>").text(mod.label);
pill.attr("title", mod.hint);
pill.data("mod-key", mod.key);
pill.on("click", function (e) {
e.preventDefault();
if (pill.prop("disabled")) return; // already used on this word
openValueEditor(mod, -1);
});
picker.append(pill);
});
}
btn.on("click", function (e) {
e.preventDefault();
// Close any other open pickers in the same list.
li.closest("ol, ul").find(".wiktku-mod-picker").not(picker).hide();
buildPills();
// Grey out modifiers already used on this word — refreshed on
// every open since mods (and thus what's "used") can change.
var used = usedKeys();
picker.find(".wiktku-mod-pill").each(function () {
var isUsed = !!used[$(this).data("mod-key")];
$(this).prop("disabled", isUsed).toggleClass("wiktku-mod-pill-used", isUsed);
});
picker.toggle();
});
// ⊕ (and the word's own remove ×) only make sense once there's an
// actual word — on the empty trailing "type here" box, a modifier
// would have nothing to attach to and there's nothing to remove.
function updateBtnVisibility() {
var hasWord = !!$.trim(wordInput.val());
btn.toggle(hasWord);
removeBtn.toggle(hasWord);
if (!hasWord) picker.hide();
}
updateBtnVisibility();
wordInput.on("input", function () {
updateBtnVisibility();
syncTa();
});
removeBtn.on("click", function () { li.remove(); });
// Lets restoreSubList's "fill an existing row" path set this word's
// value correctly (re-split into word+mods) instead of dumping the
// raw "word<key:value>" string straight into wordInput.
li.data("setRawValue", function (raw) {
var p = parseWordValue(raw);
wordInput.val(p.word);
mods = p.mods;
syncTa();
renderMods();
updateBtnVisibility();
});
// Close the picker once focus leaves the li entirely (clicking ⊕ or
// its value input blurs wordInput too, but focus stays inside li).
li.on("focusout", function () {
setTimeout(function () {
if (!$.contains(li[0], document.activeElement)) picker.hide();
}, 150);
});
// Enter moves on instead of inserting a newline into what's meant
// to be a one-line field.
wordInput.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); wordInput[0].blur(); }
});
li.append(ta).append(modsRow).append(boxRow).append(picker);
return li;
}
/**
* Builds a "chip + toggle" field (design "1c", first built for
* Dever/Şîve): existing values render as small removable chips; a
* "+ <label>" toggle opens a compact panel with optional quick-pick
* suggestion pills plus free text entry. Keeps the form from growing
* for the (usually most) senses that leave the field empty — no
* separate "advanced" section needed to hide it behind.
*
* opts: {
* wrapCls: unique class on the wrapper, e.g. "wiktku-dfn-dever" —
* getSenses()/restoreDfn() scope chip reads/writes by it,
* and it keys the persisted suggestion overrides below.
* toggleLabel: e.g. "+ Dever/Şîve"
* hint: panel's question/help line (optional)
* suggestions: array of quick-pick strings (optional)
* placeholder: free-text input placeholder (optional)
* onOpen: called right before this field's panel opens, so a
* caller can close sibling fields' panels (optional)
* }
* The panel is returned separately from wrap (not appended inside it) —
* callers place all fields' wraps together in a toggle row and all
* fields' panels together in a panel area below, so an open panel never
* shifts the position of other fields' toggles.
* Suggestion pills get a × too: removing a built-in one hides it from
* then on, and anything typed into the free-text box joins the pick
* list for next time — both persisted per wrapCls via mw.storage, not
* tied to any one page or entry.
* Returns { wrap, panel, addChip, values, setOpenDefault, close }.
*/
var WIKTKU_SUGGESTIONS_KEY_PREFIX = "wiktku_suggestions_";
function loadSuggestionOverrides(wrapCls) {
if (!(window.mw && mw.storage)) return { removed: [], added: [] };
var raw;
try { raw = mw.storage.get(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls); } catch (e) { raw = null; }
var parsed = {};
if (raw) { try { parsed = JSON.parse(raw); } catch (e) { parsed = {}; } }
return { removed: parsed.removed || [], added: parsed.added || [] };
}
function saveSuggestionOverrides(wrapCls, overrides) {
if (!(window.mw && mw.storage)) return;
try { mw.storage.set(WIKTKU_SUGGESTIONS_KEY_PREFIX + wrapCls, JSON.stringify(overrides)); } catch (e) { /* storage unavailable — override just won't persist */ }
}
function buildChipField(opts) {
var wrap = $("<div></div>").addClass("wiktku-dfn-chipfield-wrap " + opts.wrapCls + "-wrap");
var chipsRow = $("<span class='wiktku-dfn-chipfield-chips'></span>");
var toggle = $("<button type='button' class='wiktku-dfn-chipfield-toggle'></button>").text(opts.toggleLabel);
var panel = $("<div class='wiktku-dfn-chipfield-panel'></div>").hide();
var suggestRow = $("<div class='wiktku-dfn-chipfield-suggest'></div>");
var input = $("<input type='text' class='wiktku-dfn-chipfield-input'>")
.attr("placeholder", opts.placeholder || ((opts.suggestions && opts.suggestions.length) ? "An ya nû binivîse…" : "Binivîse…"));
var addBtn = $("<button type='button' class='wiktku-dfn-chipfield-add'>Qeyd bike</button>");
// Once the person opens/closes the panel themselves, lang-driven
// syncing (see setOpenDefault) backs off rather than fighting them.
var userToggled = false;
// The pick list shown is opts.suggestions with the person's own
// edits layered on top — see loadSuggestionOverrides above.
var overrides = loadSuggestionOverrides(opts.wrapCls);
var removedSuggestions = overrides.removed;
var addedSuggestions = overrides.added;
function persistOverrides() {
saveSuggestionOverrides(opts.wrapCls, { removed: removedSuggestions, added: addedSuggestions });
}
function allSuggestions() {
return $.grep(opts.suggestions || [], function (s) { return removedSuggestions.indexOf(s) === -1; })
.concat(addedSuggestions);
}
function learnSuggestion(val) {
if (!val || allSuggestions().indexOf(val) !== -1) return;
addedSuggestions.push(val);
persistOverrides();
}
function forgetSuggestion(name) {
var idx = addedSuggestions.indexOf(name);
if (idx !== -1) addedSuggestions.splice(idx, 1);
else if (removedSuggestions.indexOf(name) === -1) removedSuggestions.push(name);
persistOverrides();
renderSuggest();
// renderSuggest() just destroyed the pill's × button the click
// came from — which was focused — dropping focus outside the
// panel entirely. Left alone, that reads as "focus left the
// widget" to the focusout handler below and closes the whole
// panel. Move focus back inside before that check runs.
input.focus();
}
function values() {
var vals = [];
chipsRow.find(".wiktku-dfn-chipfield-chip").each(function () {
vals.push($(this).attr("data-value"));
});
return vals;
}
function renderSuggest() {
var taken = values();
suggestRow.empty();
$.each(allSuggestions(), function (_, name) {
if (taken.indexOf(name) !== -1) return;
var pill = $("<span role='button' class='wiktku-dfn-chipfield-pill'></span>").text(name);
pill.on("click", function () { addChip(name); input.val("").focus(); });
var pillRemove = $("<button type='button' class='wiktku-dfn-chipfield-pill-remove' title='Vê tewsiyeyê veşêre'>×</button>");
pillRemove.on("click", function (e) {
e.stopPropagation(); // don't also trigger the pill's own add-chip click
forgetSuggestion(name);
});
pill.append(pillRemove);
suggestRow.append(pill);
});
suggestRow.toggle(!!suggestRow.children().length);
}
function addChip(val) {
val = $.trim(val || "");
if (!val || values().indexOf(val) !== -1) return;
var chip = $("<span class='wiktku-dfn-chipfield-chip'></span>").attr("data-value", val).text(val);
var remove = $("<button type='button' class='wiktku-dfn-chipfield-remove' title='Jê bibe'>×</button>");
// Same focus-loss hazard as forgetSuggestion() below — refocus
// the input so removing a chip while the panel is open doesn't
// also close the panel.
remove.on("click", function () { chip.remove(); renderSuggest(); input.focus(); });
chip.append(remove);
chip.insertBefore(toggle); // chips in insertion order, toggle stays last
renderSuggest();
}
toggle.on("click", function () {
userToggled = true;
if (panel.is(":visible")) { panel.hide(); return; }
if (opts.onOpen) opts.onOpen();
renderSuggest();
panel.show();
input.focus();
});
// Anything typed and committed by hand joins the pick list too, not
// just the current sense's chips.
function commitInput() {
var val = $.trim(input.val() || "");
if (val) {
addChip(val);
learnSuggestion(val);
}
input.val("").focus();
}
addBtn.on("click", commitInput);
input.on("keydown", function (e) {
if (e.which === 13) { e.preventDefault(); commitInput(); }
});
// Close the panel once focus leaves toggle+panel together — they're
// siblings in the DOM (not nested), so both must be checked.
function focusStillInside() {
return $.contains(wrap[0], document.activeElement) || $.contains(panel[0], document.activeElement);
}
wrap.add(panel).on("focusout", function () {
setTimeout(function () {
if (!focusStillInside()) panel.hide();
}, 150);
});
if (opts.hint) panel.append($("<div class='wiktku-dfn-chipfield-hint'></div>").text(opts.hint));
panel.append(suggestRow).append(
$("<div class='wiktku-dfn-chipfield-input-row'></div>").append(input).append(addBtn)
);
chipsRow.append(toggle);
wrap.append(chipsRow);
return {
wrap: wrap,
panel: panel,
addChip: addChip,
values: values,
/** Sets the panel's default open/closed state — a no-op once
* the person has manually toggled it themselves this session. */
setOpenDefault: function (open) {
if (userToggled) return;
// Same as the toggle click handler: (re)build the suggestion
// pills whenever the panel opens, not just on a manual click —
// otherwise a panel opened by this default-state sync (e.g.
// Dever/Şîve starting open for ku) shows an empty pill row
// until the person closes and reopens it themselves.
if (open) {
if (opts.onOpen) opts.onOpen();
renderSuggest();
}
panel.toggle(!!open);
},
/** Force-closes the panel as though the person had done it
* themselves, so a later setOpenDefault() doesn't reopen it out
* from under them — used by onOpen to keep only one of these
* fields' panels open at a time (see the call site). */
close: function () {
userToggled = true;
panel.hide();
}
};
}
function makeWordListStep(id, name, question_fmt) {
return {
id: id,
name: name,
question_fmt: question_fmt,
multiple: true,
narrow: true,
modifiers: true,
// Function instead of a plain string so it can name the current
// language — computed fresh at build time and re-applied by
// refresh() whenever the language changes.
placeholder: function () {
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return "Kelîme" + (langLabel ? " bi " + langLabel : "") + "…";
},
visible: function (values) {
return values.lang && values.pos;
},
output: function (values) {
var words = $.grep(values[id] || [], function (w) { return !!w; });
var header = capitalize(name);
if (!words.length) return "";
var lang = values.lang;
var section = "\n==== " + header + " ====\n";
var params = "cure=" + header + "\n" +
$.map(words, function (w) {
return "|" + w;
}).join("\n");
return section +
"{{kol3|" + lang + "|" + params + "\n}}\n";
} };
}
var steps = {
lang: {
id: "lang",
required: true,
name: "ziman",
question_fmt: '"' + pageName + '" bi çi zimanî ye?',
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (langChoices).
choices: [],
output: function (values) {
return "== {{ziman|" + values.lang + "}} ==\n";
}
},
pron: {
id: "pron",
name: "bilêvkirin",
question_fmt: '"' + pageName + '" çawa tê telafûzkirin?',
// pronTpls populated from data.json
pronTpls: {},
visible: function (values) {
if (!values.lang) return false;
// addLangMode: skip pron only if this lang already has a section.
if (addLangMode) {
// Clearing the page makes existing content irrelevant.
var clearPageCheck = $("#wiktku-clear-page-check");
if (clearPageCheck.length && clearPageCheck.prop("checked")) {
return true;
}
var existing = $("#wpTextbox1").val() || "";
return !findLangHeader(existing, values.lang);
}
return true;
},
// Fills the insert-dropdown from pronTpls, filtered by language.
refreshTplSelect: function () {
var sel = $("#wiktku-start-pron .wiktku-tpl-insert-select");
if (!sel.length) return;
var lang = getValues().lang || "";
sel.find("option[value!='']").remove();
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-pron-wrap").length) return null;
var wrap = $("<div class='wiktku-pron-wrap'></div>");
// Reassures the user this field is optional — the dropdown
// below writes the template for them, no syntax needed up front.
wrap.append(
$("<p class='wiktku-step-hint'></p>").text(
"Ev qad ne mecbûrî ye. Heke tu nizanî kîjan şablonê hilbijêrî, vale bihêle."
)
);
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-pron-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-pron-text")
.attr("rows", 4)
.attr("placeholder", "Nimûne: {{IPA|ku|/pɛɪ̯n/}} yan {{kîte|ku|he|ce|ki|rin}}");
// Template insert dropdown — filtered by language.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike ");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var lang = getValues().lang || "";
$.each(steps.pron.pronTpls, function (name, tpl) {
// Filter: if tpl.langs exists, only show if lang is in that list; otherwise show always
var allowed = !tpl.langs || tpl.langs.indexOf(lang) !== -1;
if (allowed) {
sel.append($("<option></option>")
.val(tpl.snippet)
.text(name + " — " + tpl.desc)
.attr("data-nolang", tpl.noLang ? "1" : ""));
}
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
// Check if the selected option has noLang flag
var selectedOption = sel.find("option:selected");
var hasNoLang = selectedOption.attr("data-nolang") === "1";
if (!hasNoLang) {
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertLineSnippet(ta[0], snippet);
$(this).val("");
// Clear the picker's search box
sel.trigger("wiktku:refresh");
ta.focus();
return;
}
// The noLang templates (ku-IPA, ku-kîte, zza-IPA, zza-kîte)
// auto-generate content from a Lua module reading the page
// title, so show a live preview and require confirmation
// before inserting.
var tplLabel = selectedOption.text();
$(this).val("");
sel.trigger("wiktku:refresh");
steps.pron.showTplPreview(stepDiv, ta, snippet, tplLabel);
});
wrap.append(sel).append(ta);
stepDiv.append(wrap);
steps.pron.buildIpaPalette(wrap, ta);
initSelects(wrap);
return ta;
},
// ── IPA character palette ji [[MediaWiki:Gadget-charinsert-core.js]]────────────
// Modelled on fr.wiktionary's: every character is a button that
// drops itself in at the cursor. Two sub-tabs, because the full
// IPA chart is ~180 symbols and roughly twenty of them ever
// appear in Kurmancî — "IPA (kurdî)" opens by default and the
// full chart is one click away for loanwords and other
// languages. Grouped with a label down the left so it reads as
// a chart rather than one long line of glyphs.
IPA_KU: [
{ label: "Stres", chars: ["ˈ", "ˌ", "ː"] },
{ label: "Bêdeng", chars: ["d͡ʒ", "t͡ʃ", "ʒ", "ʃ", "ɫ", "ŋ", "ɾ", "r", "ʋ", "ħ", "ʕ", "ɣ", "χ", "ʔ"] },
{ label: "Bideng", chars: ["ɑː", "eː", "ɛ", "ɪ", "iː", "ɯ", "ʊ", "uː", "yː", "ø", "œ"] }
],
IPA_FULL: [
{ label: "Bêdeng 1", chars: ["m", "ɱ", "n", "ɳ", "ɲ", "ŋ", "ɴ", "p", "b", "t", "d", "t̪", "d̪", "ʈ", "ɖ", "c", "ɟ", "k", "ɡ", "q", "ɢ", "ʡ", "ʔ"] },
{ label: "Bêdeng 2", chars: ["ɸ", "β", "f", "v", "θ", "ð", "s", "z", "ʃ", "ʒ", "ʂ", "ʐ", "ɕ", "ʑ", "ç", "ʝ", "x", "ɣ", "χ", "ʁ", "ħ", "ʕ", "h", "ɦ", "ʜ", "ʢ", "ɧ"] },
{ label: "Bêdeng 3", chars: ["ʋ", "ɹ", "ɻ", "j", "ɥ", "ɰ", "ʍ", "w", "ʙ", "ⱱ", "ɾ", "ɽ", "r", "ʀ", "ɺ", "l", "ɫ", "ɬ", "ɮ", "ɭ", "ʎ", "ʟ"] },
{ label: "Bêdeng 4", chars: ["ɓ", "ɗ", "ᶑ", "ʄ", "ɠ", "ʛ", "ʘ", "ǀ", "ǃ", "ǂ", "ǁ"] },
{ label: "Bideng", chars: ["i", "y", "ɪ", "ʏ", "ɨ", "ʉ", "ɯ", "u", "ʊ", "e", "ø", "ɘ", "ɵ", "ɤ", "o", "ə", "ɚ", "ɛ", "œ", "ɜ", "ɝ", "ɞ", "ʌ", "ɔ", "æ", "ɶ", "ɐ", "a", "ɑ", "ɒ"] },
{ label: "Suprasegmental", chars: ["ˈ", "ˌ", "ː", "ˑ", "ʼ", "ˀ", "ˤ", "ᵝ", "ᵊ", "ˠ", "ʰ", "ʱ", "ʲ", "ˡ", "ⁿ", "ᵑ", "ʷ", "ᶣ", "˞", "‿"] },
{ label: "Dîakrîtîk", chars: ["̚", "̪", "̺", "̻", "̼", "̬", "̊", "̥", "̞", "̝", "̘", "̙", "̽", "̟", "̠", "̈", "̤", "̹", "̜", "̍", "̩", "̆", "̯", "̃", "̰", "͡", "͜"] },
{ label: "Ton", chars: ["̋", "́", "̄", "̀", "̏", "̌", "̂", "˥", "˦", "˧", "˨", "˩", "ꜛ", "ꜜ", "↗", "↘", "‖"] }
],
/** Builds the two-sub-tab character palette under the textarea. */
buildIpaPalette: function (wrap, ta) {
if (wrap.find(".wiktku-ipa-palette").length) return;
// The short Kurmancî/full IPA chart shows directly, always expanded.
var panel = $("<div class='wiktku-ipa-panel'></div>");
// Sub-tab bar, with the two template buttons pinned right.
var subTabs = $("<div class='wiktku-ipa-subtabs'></div>");
var kuTab = $("<span class='wiktku-ipa-subtab wiktku-tab-active'>IPA (kurdî)</span>");
var fullTab = $("<span class='wiktku-ipa-subtab'>IPA</span>");
subTabs.append(kuTab).append(fullTab);
var tplBtns = $("<span class='wiktku-ipa-tplbtns'></span>");
$.each([
{ label: "{{ku-IPA|+}}", snippet: "{{ku-IPA|}}", back: 2 },
{ label: "{{IPA|+}}", snippet: "{{IPA|ku|}}", back: 2 }
], function (_, t) {
var b = $("<button type='button' class='wiktku-ipa-tplbtn'></button>").text(t.label);
b.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], t.snippet);
// Park the cursor inside the braces, where the value goes.
var pos = ta[0].selectionStart - t.back;
ta[0].selectionStart = ta[0].selectionEnd = pos;
ta.focus();
});
tplBtns.append(b);
});
subTabs.append(tplBtns);
panel.append(subTabs);
/** One character key — shared by both the plain grid (every
* group's row shown at once) and the grouped grid (one
* group's row shown at a time, picked from a dropdown). */
function makeKeyBtn(ch) {
var btn = $("<button type='button' class='wiktku-ipa-key'></button>");
// A combining mark on its own renders as a stray
// accent floating over whatever precedes it, so
// show it on a dotted circle — but insert only
// the mark itself.
var combining = /^[̀-ͯ҃-҉]$/.test(ch);
btn.text(combining ? "◌" + ch : ch);
if (combining) btn.addClass("wiktku-ipa-key-combining");
btn.attr("title", ch);
btn.on("click", function (e) {
e.preventDefault();
insertAtCursor(ta[0], ch);
ta.focus();
});
return btn;
}
function buildGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid'></div>");
$.each(groups, function (_, g) {
var row = $("<div class='wiktku-ipa-row'></div>");
row.append($("<span class='wiktku-ipa-rowlabel'></span>").text(g.label));
var keys = $("<span class='wiktku-ipa-keys'></span>");
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
row.append(keys);
grid.append(row);
});
return grid;
}
// The full IPA chart runs to ~180 symbols across 8 groups —
// too long to show all at once, so a dropdown picks one group
// (Bêdeng 1, Bêdeng 2, …) and only that group's keys show.
function buildGroupedGrid(groups) {
var grid = $("<div class='wiktku-ipa-grid wiktku-ipa-grid--grouped'></div>");
var picker = $("<select class='wiktku-ipa-group-select wiktku-plain-select wiktku-start-narrow'></select>");
$.each(groups, function (i, g) {
picker.append($("<option></option>").val(i).text(g.label));
});
var keys = $("<span class='wiktku-ipa-keys'></span>");
function renderGroup(idx) {
keys.empty();
var g = groups[idx];
if (!g) return;
$.each(g.chars, function (_, ch) { keys.append(makeKeyBtn(ch)); });
}
picker.on("change", function () { renderGroup(this.selectedIndex); });
renderGroup(0);
grid.append($("<div class='wiktku-ipa-group-row'></div>").append(picker));
grid.append($("<div class='wiktku-ipa-row'></div>").append(keys));
return grid;
}
var kuGrid = buildGrid(steps.pron.IPA_KU);
var fullGrid = buildGroupedGrid(steps.pron.IPA_FULL).hide();
panel.append(kuGrid).append(fullGrid);
kuTab.on("click", function () {
kuTab.addClass("wiktku-tab-active"); fullTab.removeClass("wiktku-tab-active");
kuGrid.show(); fullGrid.hide();
});
fullTab.on("click", function () {
fullTab.addClass("wiktku-tab-active"); kuTab.removeClass("wiktku-tab-active");
fullGrid.show(); kuGrid.hide();
});
panel.addClass("wiktku-ipa-palette");
wrap.append(panel);
},
/** Inline preview + confirm panel for a "noLang" (auto-content)
* pronunciation template — fetches what the snippet expands to
* for this page and only inserts it if the user confirms. */
showTplPreview: function (stepDiv, ta, snippet, tplLabel) {
// Only one preview panel open at a time.
stepDiv.find(".wiktku-pron-preview").remove();
var panel = $("<div class='wiktku-pron-preview'></div>");
panel.append($("<div class='wiktku-pron-preview-label'></div>")
.text("Pêşdîtina " + tplLabel + ":"));
var body = $("<div class='wiktku-pron-preview-body'></div>").text("Tê barkirin…");
panel.append(body);
var actions = $("<div class='wiktku-pron-preview-actions'></div>");
var confirmBtn = $("<button type='button' class='wiktku-pron-preview-confirm'>✓ Tevlî bike</button>")
.prop("disabled", true);
var cancelBtn = $("<button type='button' class='wiktku-pron-preview-cancel'>✕ Betal bike</button>");
actions.append(confirmBtn).append(cancelBtn);
panel.append(actions);
ta.before(panel);
confirmBtn.on("click", function () {
insertLineSnippet(ta[0], snippet);
panel.remove();
ta.focus();
});
cancelBtn.on("click", function () {
panel.remove();
});
// action=parse, not action=expandtemplates. expandtemplates
// returns *expanded wikitext*, so the preview read as
// [[IPA]]<sup>([[Wîkîferheng:IPA kurmancî|kilîd]])</sup>:
// <span class="IPA">/-{kshhe}-/</span>[[Kategorî:…]] — which
// is exactly what the person is trying to avoid having to
// read. parse hands back the rendered HTML the page will
// actually show.
//
// Drop the leading "* " first: it is wikitext list syntax
// wrapping the template, and keeping it would render the
// preview as a bulleted list rather than the line itself.
new mw.Api().post({
action: "parse",
title: pageName,
text: snippet.replace(/^\*\s*/, ""),
prop: "text",
wrapoutputclass: "",
disablelimitreport: 1,
disableeditsection: 1,
contentmodel: "wikitext",
formatversion: 2
}).done(function (data) {
var html = (data && data.parse && data.parse.text) || "";
if ($.trim($("<div>").html(html).text())) {
body.html(html);
// Category links render as nothing but still exist;
// and any real link inside a preview should not
// navigate away from a half-filled form.
body.find("a").attr("target", "_blank").attr("rel", "noopener");
} else {
body.text("(vala — ji bo vê rûpelê tiştek nehate afirandin)");
}
confirmBtn.prop("disabled", false);
}).fail(function () {
body.text("Pêşdîtin nehate barkirin — tu dikarî hîn jî wê tevlî bikî.");
confirmBtn.prop("disabled", false);
});
},
output: function () {
var text = $.trim($("#wiktku-start-pron .wiktku-pron-text").val());
if (!text) return "";
var lines = text.split("\n");
var out = $.map(lines, function (line) {
line = $.trim(line);
if (!line) return undefined;
return line;
}).join("\n");
// "===" heading under Ziman and above the POS sections; the
// field is shared across sections, so it's emitted once by
// getWikitext() rather than per-POS-section (would otherwise
// print the pronunciation twice for a two-POS entry).
return "\n=== Bilêvkirin ===\n" + out + "\n";
} },
pos: {
id: "pos",
required: true,
name: "Cureyê rêzimanî",
question_fmt: 'Cureyê rêzimanî yê "' + pageName + '" çi ye?',
visible: function (values) {
return values.lang;
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (posChoices, posExamples).
choices: [],
// Definition of whichever cureyê rêzimanî is currently chosen —
// used to be the Mane textarea's placeholder, which mixed up "what
// this grammatical category means" with "what to type here" (see
// buildSenseBlock's placeholder for the latter, now generic).
// Shown as plain text right under the dropdown instead (see the
// .wiktku-start-pos-example build/refresh() above).
posExamples: {},
example: function (values) {
var pos = values.pos || "";
return steps.pos.posExamples[pos] ||
"Cureyê rêzimanî yê vê kelîmeyê hilbijêre — navdêr, lêker, rengdêr hwd.";
},
output: function (values) {
var pos = values.pos || "";
var capitalizedPos = capitalize(pos);
// "form" opt-param (checked in posparams) marks this as a
// form-of/inflected entry — the section title becomes
// "Formeke <pos>ê" instead of the plain pos name. The
// template call itself still uses the unchanged pos name.
var isForm = $("#wiktku-posparams-opt-form").is(":checked");
var header = isForm
? "\n=== Formeke " + pos + "ê ===\n"
: "\n=== " + capitalizedPos + " ===\n";
// Read the live wikitext from the posparams textarea.
// Fall back to a plain {{pos|lang}} if the step hasn't been built yet.
var tplLine = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
if (!tplLine) {
tplLine = "{{" + pos + "|" + (values.lang || "") + "}}";
}
// {{ku-tewîn-lk}} line for xwerû Kurmancî verbs, parsed straight out of tplLine (not the posparams input fields, which a cache/section restore doesn't repopulate) once niha/borî/transitivity are all present.
var tewinLkLine = "";
if (pos === "lêker" && values.lang === "ku") {
var nihaMatch = tplLine.match(/\|niha=([^|}]+)/);
var borîMatch = tplLine.match(/\|borî=([^|}]+)/);
var cMatch = tplLine.match(/\|c=([^|}]+)/);
var niha = nihaMatch ? $.trim(nihaMatch[1]) : "";
var borî = borîMatch ? $.trim(borîMatch[1]) : "";
var cParts = cMatch ? /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]) : null;
var lekturForm = cParts ? (cParts[1] || "") : "";
var lktransitivity = cParts ? cParts[2] : "";
var lkForm = lktransitivity === "gh" ? "gerguhêz" : (lktransitivity === "ngh" ? "negerguhêz" : "");
if (niha && borî && lekturForm === "x" && lkForm) {
tewinLkLine = "{{ku-tewîn-lk|" + pageName + "|form=" + lkForm + "|niha=" + niha + "|borî=" + borî + "}}\n";
}
}
// Image line (between the conjugation line, if any, and the template call)
var imageLine = steps.resim.output(values);
return header + tewinLkLine + imageLine + tplLine + "\n";
}
},
// cinsiyet (gender) — shown for navdêr/serenav only, with choices built
// from metadata[lang].g since not every language has grammatical gender.
cinsiyet: {
id: "cinsiyet",
name: "cinsiyet",
question_fmt: 'Cinsiyeta "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
// Single source of truth for the selected gender code. The visible
// dropdown lives inside the posparams step (see steps.posparams),
// but it must always read/write THIS hidden field — not a field of
// its own — since getValuesForStep("cinsiyet") reads from here.
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-cinsiyet-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// jimar (grammatical number) — same hidden-field pattern as cinsiyet
// above, but writes into its own j= template parameter instead of
// sharing c=. Shown for navdêr/serenav only, with the same choices
// for every language — data.json's numberLabels (see syncHelpers).
jimar: {
id: "jimar",
name: "jimar",
question_fmt: 'Jimara "' + pageName + '" çi ye?',
visible: function () { return false; }, // handled inside posparams
build: function (stepDiv) {
var field = stepDiv.find(".wiktku-start-step-field");
if (field.length) return field;
field = $("<input type='hidden'>")
.addClass("wiktku-start-step-field")
.attr("id", "wiktku-start-jimar-field")
.val("");
stepDiv.append(field);
return field;
},
output: function () { return ""; } // consumed by posparams/pos.output
},
// Lêker transitivity — hidden step, handled inside posparams
lektur: {
id: "lektur",
name: "Cureyê lêkerê (fiîlê)",
question_fmt: "Cureyê lêkerê (fiîlê) çi ye?",
visible: function () { return false; },
choices: [
{ code: "gh", label: "gerguhêz" },
{ code: "x-gh", label: "xwerû-gerguhêz" },
{ code: "p-gh", label: "pêkhatî-gerguhêz" },
{ code: "h-gh", label: "hevedudanî-gerguhêz" },
{ code: "ngh", label: "negerguhêz" },
{ code: "x-ngh", label: "xwerû-negerguhêz" },
{ code: "p-ngh", label: "pêkhatî-negerguhêz" },
{ code: "h-ngh", label: "hevedudanî-negerguhêz" } ],
output: function () { return ""; }
},
// tr — hidden step, handled inside posparams
tr: {
id: "tr",
name: "transliterasyon",
question_fmt: "Transliterasyona vê kelîmeyê",
multiple: false,
narrow: true,
visible: function () { return false; }
},
posparams: {
id: "posparams",
// Opt-panel checkboxes that write a different template param/value than the default "key=1" -- form and neqiyasbar both write to the shared |form=| param.
OPT_PARAM_OVERRIDES: {
form: { param: "form", value: "erê" },
neqiyasbar: { param: "form", value: "-" }
},
// Fused onto the "pos" card visually (see #wiktku-start-posparams
// in styles.css) so the two read as one question — the
// "xisûsiyet" name above is a no-CSS/no-JS fallback only,
// since its h3 is hidden by CSS, and this step has no question
// text of its own either: each field below is already
// self-labelled, and repeating it would just echo "Cureyê
// rêzimanî" one card up (see the pos step above, fused flush
// against this one — no gap, no duplicate heading).
name: "xisûsiyet",
question_fmt: "",
visible: function (values) {
return !!(values.lang && values.pos);
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-posparams-wrap").length) return null;
var wrap = $("<div class='wiktku-posparams-wrap'></div>");
var helpers = $("<div class='wiktku-posparams-helpers'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-posparams-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-posparams-text")
.attr("rows", 2);
var _rebuilding = false;
function safeRebuild() {
if (_rebuilding) return;
_rebuilding = true;
steps.posparams.rebuildTa(stepDiv);
_rebuilding = false;
refresh();
}
// refresh() walks every sense block and nimûne item, so
// running safeRebuild() on every keystroke visibly lags
// typing once an entry has a few sense blocks — debounce
// text-row input so it only fires once typing pauses.
var REBUILD_DEBOUNCE_MS = 200;
var rebuildDebounceTimer = null;
function debouncedRebuild() {
clearTimeout(rebuildDebounceTimer);
rebuildDebounceTimer = setTimeout(safeRebuild, REBUILD_DEBOUNCE_MS);
}
// Generic text-input row factory
function makeTextRow(key, labelText, placeholder) {
var row = $("<div class='wiktku-posparams-row'></div>")
.attr("id", "wiktku-posparams-row-" + key).hide();
var inp = $("<input type='text'>")
.addClass("wiktku-start-narrow wiktku-posparams-field")
.attr("data-pp-key", key)
.attr("placeholder", placeholder || "");
inp.on("input", debouncedRebuild);
row.append($("<label></label>").text(labelText + ": ")).append(inp);
return row;
}
// cinsiyet
var cinsiyetRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-c'></div>")
.append($("<label></label>").text("Cinsiyet: ")).hide();
var cinsiyetSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-cinsiyet-sel")
.append($("<option value=''>— hilbijêre —</option>"));
// Writes into the real, shared #wiktku-start-cinsiyet-field (owned by
// steps.cinsiyet) rather than a field of its own — see the comment
// on steps.cinsiyet.build for why that matters.
cinsiyetSel.on("change", function () {
$("#wiktku-start-cinsiyet-field").val($(this).val()).trigger("change");
safeRebuild();
});
cinsiyetRow.append(cinsiyetSel);
// jimar (number) — lives in "Tercîhên pêşketî" (rarely needed),
// writes into the shared hidden #wiktku-start-jimar-field, into
// its own j= param instead of sharing c=. Options are built in
// syncHelpers once the language's number codes are known.
var jimarRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-jimar'></div>")
.append($("<label></label>").text("Jimar: ")).hide();
var jimarSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-jimar-sel")
.append($("<option value=''>— hilbijêre —</option>"));
jimarSel.on("change", function () {
$("#wiktku-start-jimar-field").val($(this).val()).trigger("change");
safeRebuild();
});
jimarRow.append(jimarSel);
// lektur: two plain questions (transitivity + structural form)
// instead of one 10-option dropdown. Emitted code: base alone
// ("gh"), or prefix + "-" + base ("x-gh").
var lekturRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lektur'></div>")
.append($("<label></label>").text("Gerguhêziya lêkerê çawa ye? ")).hide();
var lekturSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lektur-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='gh'>gerguhêz (îsmî mefûl digire)</option>"))
.append($("<option value='ngh'>negerguhêz (îsmî mefûl nagire)</option>"));
lekturSel.on("change", safeRebuild);
lekturRow.append(lekturSel);
// Second half of the same question — the structural form.
// Blank = xwerû/sade, which is the common case; shown
// alongside lektur (not gated on it being answered first —
// rebuildTa() only uses this once lektur has a value anyway).
var lekturFormRow = $("<div class='wiktku-posparams-row' id='wiktku-posparams-row-lekturform'></div>")
.append($("<label></label>").text("Şiklê lêkerê: ")).hide();
var lekturFormSel = $("<select></select>")
.addClass("wiktku-plain-select wiktku-start-narrow wiktku-posparams-lekturform-sel")
.append($("<option value=''>— hilbijêre —</option>"))
.append($("<option value='x'>xwerû</option>"))
.append($("<option value='p'>pêkhatî</option>"))
.append($("<option value='h'>hevedudanî</option>"));
lekturFormSel.on("change", safeRebuild);
lekturFormRow.append(lekturFormSel);
helpers.append(cinsiyetRow).append(lekturRow).append(lekturFormRow);
// ── Everything below here is "Tercîhên pêşketî" ──────────────
// These rows live inside a collapsed panel, so the default
// view of this card is two dropdowns instead of twenty-odd fields.
// Transliteration is a primary field, not an advanced one, for
// any language not written in Latin script: for those, an
// entry without it is incomplete. syncHelpers already gates
// the row on needsTransliteration(lang), so for Kurmancî and
// other Latin-script languages it simply never appears.
helpers.append(makeTextRow("tr", "Transliterasyon (tr)", "Transliterasyon…"));
// fsubj/fdbê/fniha/fborî: the actual conjugated forms (as
// opposed to niha/borî/subj below, which are the bare roots
// the Lua module conjugates from) — primary fields for
// lêker, not tucked behind "Tercîhên pêşketî", same
// reasoning as tr above. fniha/fborî show for every lêker
// language; fdbê (forma dema bê) only for ku, and fsubj
// (fermanî) only outside ku — see the isLeker/isKuLeker/
// isLekerSubj toggles in syncHelpers.
var tewandinHeading = $("<div class='wiktku-posparams-subheading' id='wiktku-posparams-tewandin-heading'></div>")
.text("Tewandina lêkerê").hide();
helpers.append(tewandinHeading);
var fnihaRow = makeTextRow("fniha", "Forma dema niha (fniha)", "Forma dema niha…");
var fborîRow = makeTextRow("fborî", "Forma dema borî (fborî)", "Forma dema borî…");
var fdbêRow = makeTextRow("fdbê", "Forma dema bê (fdbê)", "Forma dema bê…");
var fsubjRow = makeTextRow("fsubj", "Forma dema fermanî (fsubj)", "Forma dema fermanî…");
// ez/min hint (Kurmancî only, see syncHelpers): Kurmanji's split
// ergativity puts the present-tense subject and (for
// negerguhêz verbs) the past-tense subject in the nominative
// ("ez"), but a gerguhêz verb's past-tense subject is oblique
// ("min") — so fborî's hint tracks the transitivity answer
// above while fniha's stays fixed at "ez". fdbê gets its own
// fixed "ew ê" hint instead — third person, so it doesn't
// read like fsubj/fermanî's implied second-person "tu".
// Wrapped together with the box in its own flex line
// (.wiktku-posparams-field-line) so the hint sits right
// before the box even when the row itself stacks
// label-over-field (desktop grid layout).
function addPronounHint(row, hintId, hintText) {
var inp = row.find(".wiktku-posparams-field");
var hint = $("<span class='wiktku-posparams-pronoun-hint' id='" + hintId + "'></span>").text(hintText || "").hide();
var line = $("<span class='wiktku-posparams-field-line'></span>");
inp.before(line);
line.append(hint).append(inp);
}
addPronounHint(fnihaRow, "wiktku-posparams-fniha-hint", "ez");
addPronounHint(fborîRow, "wiktku-posparams-fborî-hint", "");
addPronounHint(fdbêRow, "wiktku-posparams-fdbê-hint", "ew ê");
helpers.append(fnihaRow);
helpers.append(makeTextRow("fnihatr", "Translit. forma dema niha", "Transliterasyona forma dema niha…"));
helpers.append(fdbêRow);
helpers.append(makeTextRow("fdbêtr", "Translit. forma dema bê", "Transliterasyona forma dema bê…"));
helpers.append(fborîRow);
helpers.append(makeTextRow("fborîtr", "Translit. forma dema borî", "Transliterasyona forma dema borî…"));
helpers.append(fsubjRow);
helpers.append(makeTextRow("fsubjtr", "Translit. forma dema fermanî", "Transliterasyona forma dema fermanî…"));
var advPanel = $("<div class='wiktku-posparams-adv-panel'></div>").hide();
advPanel
.append(jimarRow)
.append(makeTextRow("cuda", "nîşankirina alternatîv", "cuda…"))
.append(makeTextRow("ts", "Transkrîpsiyon (ts)", "Transkrîpsiyon…"))
.append(makeTextRow("m", "Forma mê (m)", "Forma mê…"))
.append(makeTextRow("mtr", "Translit. mê (mtr)", "Transliterasyona mê…"))
.append(makeTextRow("n", "Forma nêr (n)", "Forma nêr…"))
.append(makeTextRow("ntr", "Translit. nêr (ntr)", "Transliterasyona nêr…"))
.append(makeTextRow("p", "Forma pirjimar (p)", "Forma pirjimar…"))
.append(makeTextRow("ptr", "Translit. pirjimar", "Transliterasyona pirjimar…"))
.append(makeTextRow("y", "Forma yekjimar (y)", "Forma yekjimar…"))
.append(makeTextRow("ytr", "Translit. yekjimar", "Transliterasyona yekjimar…"))
.append(makeTextRow("sûp", "Forma sûperlatîv (sûp)", "Forma sûperlatîv…"))
.append(makeTextRow("sûptr", "Translit. sûperlatîv", "Transliterasyona sûperlatîv…"))
.append(makeTextRow("komp", "Forma komparatîv (komp)", "Forma komparatîv…"))
.append(makeTextRow("komptr", "Translit. komparatîv", "Transliterasyona komparatîv…"))
.append(makeTextRow("gir", "Forma girdêk (gir)", "gir=…"))
.append(makeTextRow("girtr", "Translit. gir", "girtr=…"))
.append(makeTextRow("hûr", "Forma hûrek (hûr)", "hûr=…"))
.append(makeTextRow("hûrtr", "Translit. hûr", "hûrtr=…"))
.append(makeTextRow("hanja", "Forma hanja", "hanja=…"))
.append(makeTextRow("hanjaTr","Translit. hanja", "hanjaTr=…"))
// Lêker rehên demê (only shown when pos is lêker — see syncHelpers)
.append(makeTextRow("niha", "Rehê dema niha (niha)", "Rehê dema niha…"))
.append(makeTextRow("nihatr", "Translit. rehê dema niha", "Transliterasyona rehê dema niha…"))
.append(makeTextRow("borî", "Rehê dema borî (borî)", "Rehê dema borî…"))
.append(makeTextRow("borîtr", "Translit. rehê dema borî", "Transliterasyona rehê dema borî…"))
.append(makeTextRow("subj", "Rehê dema fermanî (subj)", "Rehê dema fermanî…"))
.append(makeTextRow("subjtr", "Translit. rehê dema fermanî", "Transliterasyona rehê dema fermanî…"));
// Optional boolean params — nested inside the advanced panel
// (they were already the most niche thing on the card).
var optPanel = $("<div class='wiktku-posparams-opt-panel'></div>");
$.each([
{ key: "form", label: "form — ev made forma tewandî ye" },
{ key: "formaalt", label: "formaalt — vê madeyê bike forma alternatîv" },
{ key: "nekatcure", label: "nekatcure — sinifa rêzimanî kategorîze neke" },
{ key: "nekatcins", label: "nekatcins — kategoriya cinsiyetê neyîne" },
{ key: "nekatçendterm", label: "nekatçendterm — kategoriya çend-term neyîne" },
{ key: "serlînkneke", label: "serlînkneke — sernavê madeyê lînk neke" },
{ key: "nekatpalîndrom", label: "nekatpalîndrom — kategoriya palîndromê neyîne" },
{ key: "neformtr", label: "neformtr — transliterasyona forman neyîne" }
], function (_, p) {
var cb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-" + p.key).val(p.key).on("change", safeRebuild);
var lbl = $("<label></label>").attr("for", "wiktku-posparams-opt-" + p.key).text(" " + p.label);
optPanel.append($("<div class='wiktku-posparams-opt-row'></div>").append(cb).append(lbl));
});
// "neqiyasbar" ("not comparable") -- rengdêr/hoker only (see syncHelpers), and shares the "form" checkbox's |form=| param with a different value, so the two can never both be checked.
var formCb = optPanel.find("#wiktku-posparams-opt-form");
var neqiyasbarCb = $("<input type='checkbox'>")
.attr("id", "wiktku-posparams-opt-neqiyasbar").val("neqiyasbar");
var neqiyasbarLbl = $("<label></label>").attr("for", "wiktku-posparams-opt-neqiyasbar")
.append(document.createTextNode(" neqiyasbar — ev "))
.append($("<span></span>").addClass("wiktku-posparams-opt-neqiyasbar-pos"))
.append(document.createTextNode(" ne qiyasbar e"));
optPanel.append(
$("<div id='wiktku-posparams-row-neqiyasbar' class='wiktku-posparams-opt-row'></div>")
.append(neqiyasbarCb).append(neqiyasbarLbl)
);
function syncFormExclusive(el) {
if (el === formCb[0] && formCb.is(":checked")) neqiyasbarCb.prop("checked", false);
if (el === neqiyasbarCb[0] && neqiyasbarCb.is(":checked")) formCb.prop("checked", false);
}
formCb.off("change").on("change", function () { syncFormExclusive(this); safeRebuild(); });
neqiyasbarCb.on("change", function () { syncFormExclusive(this); safeRebuild(); });
advPanel.append(optPanel);
var optToggle = $("<a href='#' class='wiktku-posparams-opt-toggle wiktku-adv-toggle'></a>")
.attr("aria-expanded", "false")
.append($("<span class='wiktku-adv-toggle-chevron'>▾</span>"))
.append($("<span class='wiktku-adv-toggle-text'>Tercîhên pêşketî</span>"));
optToggle.on("click", function (e) {
e.preventDefault();
var open = advPanel.is(":visible");
advPanel.toggle(!open);
$(this).attr("aria-expanded", open ? "false" : "true")
.toggleClass("wiktku-adv-open", !open);
});
helpers.append($("<div class='wiktku-adv-toggle-row'></div>")
.append(optToggle));
helpers.append(advPanel);
wrap.append(helpers).append(ta);
// Typing directly in textarea keeps cinsiyet/jimar hidden
// fields in sync, but does NOT call refresh() — preserves
// manual edits.
ta.on("input", function () {
var text = $(this).val();
var cMatch = text.match(/\|c=([^|}\s]+)/);
$("#wiktku-start-cinsiyet-field").val(cMatch ? cMatch[1] : "");
var jMatch = text.match(/\|j=([^|}\s]+)/);
$("#wiktku-start-jimar-field").val(jMatch ? jMatch[1] : "");
});
stepDiv.append(wrap);
return null;
},
/** Reads all helper widget values and rewrites the textarea.
* Called only by safeRebuild (user interaction) — never by syncHelpers,
* so manual textarea edits are never overwritten by a refresh cycle. */
rebuildTa: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
if (!pos || !lang) return;
var cinsiyet = stepDiv.find(".wiktku-posparams-cinsiyet-sel").val() || "";
var jimar = stepDiv.find(".wiktku-posparams-jimar-sel").val() || "";
// Two dropdowns, one code: "gh" / "ngh" on their own, or
// prefixed with the structural form ("x-gh", "b-ngh", …).
var lekturBase = stepDiv.find(".wiktku-posparams-lektur-sel").val() || "";
var lekturForm = stepDiv.find(".wiktku-posparams-lekturform-sel").val() || "";
var lektur = lekturBase ? (lekturForm ? lekturForm + "-" + lekturBase : lekturBase) : "";
function v(key) {
return $.trim(stepDiv.find(".wiktku-posparams-field[data-pp-key='" + key + "']").val());
}
var out = "{{" + pos + "|" + lang;
if (pos === "navdêr" || pos === "serenav") {
out += cinsiyet ? "|c=" + cinsiyet : (lang === "ku" ? "|c=?" : "");
// Number is its own j= param, separate from c= (which
// holds gender, including compounds like m-p/n-p).
if (jimar) out += "|j=" + jimar;
} else if (/^lêker/.test(pos) && lektur) {
out += "|c=" + lektur;
}
if (v("cuda")) out += "|" + v("cuda");
if (v("tr")) out += "|tr=" + v("tr");
if (v("ts")) out += "|ts=" + v("ts");
$.each(["m","n","p","y"], function (_, k) {
if (v(k)) out += "|" + k + "=" + v(k);
if (v(k+"tr")) out += "|" + k + "tr=" + v(k+"tr");
});
if (v("sûp")) out += "|sûp=" + v("sûp");
if (v("sûptr")) out += "|sûptr=" + v("sûptr");
if (v("komp")) out += "|komp=" + v("komp");
if (v("komptr")) out += "|komptr=" + v("komptr");
if (v("gir")) out += "|gir=" + v("gir");
if (v("girtr")) out += "|girtr=" + v("girtr");
if (v("hûr")) out += "|hûr=" + v("hûr");
if (v("hûrtr")) out += "|hûrtr=" + v("hûrtr");
if (v("hanja")) out += "|hanja=" + v("hanja");
if (v("hanjaTr")) out += "|hanjaTr=" + v("hanjaTr");
if (v("niha")) out += "|niha=" + v("niha");
if (v("nihatr")) out += "|nihatr=" + v("nihatr");
if (v("borî")) out += "|borî=" + v("borî");
if (v("borîtr")) out += "|borîtr=" + v("borîtr");
if (v("subj")) out += "|subj=" + v("subj");
if (v("subjtr")) out += "|subjtr=" + v("subjtr");
if (v("fniha")) out += "|fniha=" + v("fniha");
if (v("fnihatr")) out += "|fnihatr=" + v("fnihatr");
if (v("fborî")) out += "|fborî=" + v("fborî");
if (v("fborîtr")) out += "|fborîtr=" + v("fborîtr");
if (v("fdbê")) out += "|fdbê=" + v("fdbê");
if (v("fdbêtr")) out += "|fdbêtr=" + v("fdbêtr");
if (v("fsubj")) out += "|fsubj=" + v("fsubj");
if (v("fsubjtr")) out += "|fsubjtr=" + v("fsubjtr");
stepDiv.find(".wiktku-posparams-opt-panel input:checked").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
out += "|" + (override ? override.param + "=" + override.value : key + "=1");
});
out += "}}";
stepDiv.find(".wiktku-posparams-text").val(out);
$("#wiktku-start-cinsiyet-field").val(cinsiyet);
$("#wiktku-start-jimar-field").val(jimar);
},
/** Called from refresh() — controls row visibility only.
* Does NOT rewrite textarea so user edits are preserved.
* Rebuilds only when textarea is empty (first pos selection). */
syncHelpers: function (stepDiv) {
var values = getValues();
var pos = values.pos || "";
var lang = values.lang || "";
var needsTr = needsTransliteration(lang);
var isNom = pos === "navdêr" || pos === "serenav";
var isAdj = pos === "rengdêr" || pos === "rengdêr";
var isTip = pos === "tîp";
var isJa = lang === "ja";
var isLeker = pos === "lêker";
// Kurmancî verbs are conjugated from the present/past roots
// alone — there's no separate subjunctive root the way some
// other languages have — so this pair of fields is noise for
// lang=ku specifically, unlike niha/borî which every lêker
// language needs.
var isLekerSubj = isLeker && lang !== "ku";
var isKuLeker = isLeker && lang === "ku";
// metadata[lang].g lists gender codes (and gender+plural
// compounds like m-p/n-p, which aren't NUMBER_LABELS keys) —
// genuinely per-language, so Cîns only offers whatever this
// language's own metadata defines (see isNumberCode, which
// filters out any number code that ended up in there too).
// Jimar, by contrast, is the same set of grammatical-number
// codes for every language — data.json's numberLabels.
var meta = metadata[lang];
var allCodes = (meta && meta.g) || [];
var genderCodes = $.grep(allCodes, function (c) { return !isNumberCode(c); });
var jimarCodes = Object.keys(NUMBER_LABELS);
var showC = (pos === "navdêr" || pos === "serenav") && genderCodes.length > 0;
var cRow = stepDiv.find("#wiktku-posparams-row-c");
cRow.toggle(showC);
if (showC) {
var sel = cRow.find(".wiktku-posparams-cinsiyet-sel");
// Prefer the select's own current value (normal in-session
// use); fall back to the shared hidden field, which is how
// a cache/section restore communicates a saved gender
// before this select has any <option>s to hold it.
var prev = sel.val() || $("#wiktku-start-cinsiyet-field").val() || "";
sel.find("option[value!='']").remove();
$.each(genderCodes, function (_, code) {
sel.append($("<option></option>").val(code).text(genderLabel(code)));
});
if (prev) sel.val(prev);
}
var showJimar = (pos === "navdêr" || pos === "serenav") && jimarCodes.length > 0;
var jimarRowEl = stepDiv.find("#wiktku-posparams-row-jimar");
jimarRowEl.toggle(showJimar);
if (showJimar) {
var jimarSelEl = jimarRowEl.find(".wiktku-posparams-jimar-sel");
var prevJimar = jimarSelEl.val() || $("#wiktku-start-jimar-field").val() || "";
jimarSelEl.find("option[value!='']").remove();
$.each(jimarCodes, function (_, code) {
var opt = $("<option></option>").val(code).text(numberOptionLabel(code));
jimarSelEl.append(opt);
});
if (prevJimar) jimarSelEl.val(prevJimar);
}
stepDiv.find("#wiktku-posparams-row-lektur").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-lekturform").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-tr").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-ts").toggle(needsTr);
stepDiv.find("#wiktku-posparams-row-m").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-mtr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-n").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ntr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-p").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ptr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-y").toggle(isNom);
stepDiv.find("#wiktku-posparams-row-ytr").toggle(isNom && needsTr);
stepDiv.find("#wiktku-posparams-row-sûp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-sûptr").toggle(isAdj && needsTr);
stepDiv.find("#wiktku-posparams-row-komp").toggle(isAdj);
stepDiv.find("#wiktku-posparams-row-komptr").toggle(isAdj && needsTr);
// neqiyasbar ("not comparable") only applies to rengdêr/hoker -- hidden and cleared for every other pos.
var isCompar = isAdj || pos === "hoker";
stepDiv.find("#wiktku-posparams-row-neqiyasbar").toggle(isCompar);
if (isCompar) {
stepDiv.find(".wiktku-posparams-opt-neqiyasbar-pos").text(pos);
} else {
stepDiv.find("#wiktku-posparams-opt-neqiyasbar").prop("checked", false);
}
stepDiv.find("#wiktku-posparams-row-gir").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-girtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hûr").toggle(isTip);
stepDiv.find("#wiktku-posparams-row-hûrtr").toggle(isTip && needsTr);
stepDiv.find("#wiktku-posparams-row-hanja").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-hanjaTr").toggle(isJa);
stepDiv.find("#wiktku-posparams-row-niha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-nihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-borî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-borîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-subj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-subjtr").toggle(isLekerSubj && needsTr);
// fniha/fborî are shown for every lêker language. fsubj
// (fermanî) is skipped for ku, same as subj/subjtr above —
// fdbê (forma dema bê) covers that slot in Kurmancî's own
// conjugation instead.
stepDiv.find("#wiktku-posparams-tewandin-heading").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fniha").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fnihatr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fborî").toggle(isLeker);
stepDiv.find("#wiktku-posparams-row-fborîtr").toggle(isLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fdbê").toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-row-fdbêtr").toggle(isKuLeker && needsTr);
stepDiv.find("#wiktku-posparams-row-fsubj").toggle(isLekerSubj);
stepDiv.find("#wiktku-posparams-row-fsubjtr").toggle(isLekerSubj && needsTr);
// ez/min hints only make sense for Kurmancî itself — fborî's
// hint follows the transitivity answer (gerguhêz → "min",
// otherwise "ez"); fniha's is always "ez"; fdbê's is always
// "ew ê" (isKuLeker already gates fdbê to ku alone).
stepDiv.find("#wiktku-posparams-fniha-hint").toggle(isKuLeker);
var lekturVal = stepDiv.find(".wiktku-posparams-lektur-sel").val();
stepDiv.find("#wiktku-posparams-fborî-hint")
.text(lekturVal === "gh" ? "min" : "ez")
.toggle(isKuLeker);
stepDiv.find("#wiktku-posparams-fdbê-hint").toggle(isKuLeker);
if (!$.trim(stepDiv.find(".wiktku-posparams-text").val())) {
steps.posparams.rebuildTa(stepDiv);
}
},
output: function () { return ""; } // pos.output reads the textarea directly
},
dfn: {
id: "dfn",
name: "Mane",
// Field types that live behind "Malûmatên din". Kept as one
// list so the collapse CSS, the count badge and the allGroups
// adv: flags below can't drift apart.
ADV_SUBGROUPS: [
"wiktku-dfn-etiket",
"wiktku-dfn-kategori",
"wiktku-dfn-alternativ",
"wiktku-dfn-koordine",
"wiktku-dfn-jornav",
"wiktku-dfn-jernav"
],
question_fmt: function (values) {
var base = "Maneya kelîmeyê binivîse. Mane otomatîk tê lînkkirin!";
if (values.lang === "ku") {
return base + " Ji bo tercimeyan beşa 'Werger' bi kar bîne!";
}
return base;
},
multiple: true,
ordered: true,
rows: 3,
visible: function (values) {
return values.lang && values.pos;
},
// Builds one sense block (<li>): mane textarea, nimûne list, and
// sub-lists for alternatîv/hevmane/dijmane/etc.
buildSenseBlock: function () {
var block = $("<li class='wiktku-dfn-sense'></li>");
// — Mane textarea (declared first so the template handler can reference it) —
var dfnArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-mane-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-start-step-field wiktku-dfn-mane")
.attr("rows", 6)
.attr("placeholder", "Maneya vê kelîmeyê binivîse…");
// Deliberately does nothing on keystrokes. The only thing a live
// Mane edit needs to propagate is the Werger/Bi zaravayên din
// gloss list (glossList()/build() re-read every sense in the
// DOM, and get slow with more than one sense around) — mane
// itself isn't a required step, so nothing else in refresh()
// depends on it changing mid-keystroke. Deferred to blur
// instead; getWikitext()/output() always re-read the DOM fresh
// regardless, so the final wikitext is correct either way.
dfnArea.on("blur", function () { refresh(); });
// — Tercime + Îzah boxes (non-ku alternative to the plain mane box) —
// Non-ku entries get a direct translation plus a {{îzah|…}}-wrapped
// gloss instead of a Kurdish-style gloss. Rather than teach every
// consumer (getSenses/getOutput/linkify) a second data shape, these
// boxes just write their combined text into dfnArea and re-fire its
// "input" event, so dfnArea stays the single source of truth.
var tercimeArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-tercime-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-tercime")
.attr("rows", 2)
.attr("placeholder", "Tercimeyên rasterast ên kurmancî… Ji bo school-a îngilîzî li vir 'mekteb, medrese' binivîse");
var izahArea = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-izah-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", "wiktku-dfn-subfield wiktku-dfn-izah")
.attr("rows", 3)
.attr("placeholder", "Îzaha kurmancî… Ji bo school-a îngilîzî li vir 'cihê tehsîlgirtinê' binivîse");
var nonKuWrap = $("<div class='wiktku-dfn-nonku-wrap'></div>")
.append(tercimeArea).append(izahArea)
.hide();
function syncNonKuToMane() {
var t = $.trim(tercimeArea.val());
var iz = $.trim(izahArea.val());
// if tercime is blank, use îzah as definition
var combined = t ? (iz ? t + " {{îzah|" + iz + "}}" : t) : iz;
dfnArea.val(combined);
// Re-fires onDfnInput, as if this were typed directly into
// the mane textarea.
dfnArea.trigger("input");
}
tercimeArea.on("input", syncNonKuToMane);
izahArea.on("input", syncNonKuToMane);
// Same deferred-refresh treatment as dfnArea itself — these two
// feed into it (see syncNonKuToMane above).
tercimeArea.on("blur", function () { refresh(); });
izahArea.on("blur", function () { refresh(); });
// Switches between the plain mane box and the tercime+îzah pair —
// only for non-ku entries with no tplSelect selected.
function updateManeMode() {
var lang = getValues().lang || "";
var isTpl = !!tplSelect.val();
if (lang && lang !== "ku" && !isTpl) {
dfnArea.hide();
nonKuWrap.show();
} else {
nonKuWrap.hide();
if (!isTpl) dfnArea.show();
}
}
// Store on the block so refresh() can re-sync every block when
// lang changes, and restoreDfn() can re-sync after restoring.
block.data("updateManeMode", updateManeMode);
// — Template selector (optional — replaces mane when chosen) —
// Template definitions loaded from [[MediaWiki:Gadget-start.js/data.json]].
var tplDefs = steps.dfn.tplDefs || {};
// Cache of all non-placeholder option elements, so updateTplOptions
// can detach/re-attach them without losing the full list.
var _allTplOptions = [];
var tplSelect = $(mw.html.element("select", { "class": "wiktku-dfn-tpl-select" }));
tplSelect.attr("data-placeholder", "Li şablonekê bigere");
tplSelect.append($("<option value=''>— yan jî şablonek hilbijêre —</option>"));
$.each(tplDefs, function (name, tpl) {
// Shows "name — tpl.desc" when a tplDefs entry has a desc
// in data.json, same pattern as steps.pron's template picker.
var opt = $("<option></option>")
.val(name)
.text(tpl.desc ? name + " — " + tpl.desc : name)
.attr("data-langs", tpl.langs ? tpl.langs.join(",") : "")
.attr("data-nolang", tpl.noLang ? "1" : "");
tplSelect.append(opt);
_allTplOptions.push(opt[0]);
});
// Same status-dot / 🔗 / ⓘ row treatment as every other
// template picker. This one carries the bare name in value=,
// so the name mapper is the identity rather than a snippet
// parse; descriptions come from each template's TemplateData.
markTemplateOptions(tplSelect, function (opt) { return opt.val(); });
function updateTplOptions() {
var lang = getValues().lang || "";
var currentVal = tplSelect.val();
var currentStillValid = false;
// Detach all non-placeholder options, then re-attach only the visible
// ones — native <select> won't reliably hide options any other way.
tplSelect.find("option[value!='']").detach();
$.each(_allTplOptions, function (_, opt) {
var allowed = $(opt).attr("data-langs");
var visible = !allowed || allowed.split(",").indexOf(lang) !== -1;
if (visible) {
tplSelect.append(opt);
if ($(opt).val() === currentVal) currentStillValid = true;
}
});
// Options that were detached when the descriptions arrived
// missed that pass, so describe whatever is attached now.
// The cache is warm by this point, so this is synchronous.
applyTplDescriptions(tplSelect);
tplSelect.trigger("wiktku:refresh");
if (currentVal && !currentStillValid) {
tplSelect.val("");
tplTitleInput.hide().val("");
updateManeMode();
}
}
// Store updater on the element so refresh() can call it for all blocks.
tplSelect.data("updateOptions", updateTplOptions);
var tplTitleInput = $("<input type='text'>")
.addClass("wiktku-dfn-tpl-title wiktku-start-narrow")
.attr("placeholder", "Navê rûpela hedefê…")
.hide();
// Same reasoning as dfnArea above: refresh() (which rebuilds the
// Werger/Bi zaravayên din gloss list) is deferred to blur
// rather than run on every keystroke.
tplTitleInput.on("blur", function () { refresh(); });
// Şablon senses have no plain gloss, so hide alternatîv/hevmane/
// dijmane/etc. Etîket, Nimûne and Jêgirtin stay visible either
// way. The "Malûmatên din" accordion holds nothing but these
// groups, so it hides too instead of opening onto an empty box.
function updateFieldVisibility() {
var isTpl = !!tplSelect.val();
$.each(HIDDEN_ON_TPL_GROUPS, function (_, cls) {
block.find("." + cls + "-group").toggle(!isTpl);
});
advBox.toggle(!isTpl);
}
// Store on the block so restoreDfn() can re-sync visibility
// after setting tplSelect's value.
block.data("updateFieldVisibility", updateFieldVisibility);
tplSelect.on("change", function () {
var chosen = $(this).val();
if (chosen) {
dfnArea.hide();
nonKuWrap.hide();
tplTitleInput.show().focus();
} else {
tplTitleInput.hide().val("");
updateManeMode();
}
updateFieldVisibility();
// Re-run refresh so the Werger step visibility updates.
refresh();
});
// The template picker is an alternative to writing a
// definition, so this quiet link sits above the Mane box
// rather than getting buried under Dever/Etîket/Kategorî.
// It only expands for the minority of senses that are a
// pointer at another word (guharto, şaşnivîs, kevnbûyî)
// rather than a meaning.
var tplRow = $("<div></div>").addClass("wiktku-dfn-tpl-row")
.append(tplSelect).append(" ").append(tplTitleInput)
.hide();
var tplLink = $("<a href='#' class='wiktku-dfn-tpl-link'></a>")
.text("Maneyê bi şablonan çêke (rastnivîs, şasnivîs, awayekî din)");
tplLink.on("click", function (e) {
e.preventDefault();
tplRow.show();
$(this).hide();
tplRow.find("input.wiktku-ac-input").first().focus();
});
/** Opens the picker without a click — used on restore, when a
* sense already has a template and hiding it would make the
* chosen value invisible. */
function revealTplRow() { tplRow.show(); tplLink.hide(); }
block.data("revealTplRow", revealTplRow);
// — Dever/Şîve, Etîket, Kategorî: chip + toggle fields ("1c"
// design) — a small "+ …" toggle on the sense rather than an
// always-visible field, so senses that leave them empty (most
// of them) don't get a longer form. See getOutput() for how
// Dever/Şîve folds into {{dever|}} (ku) or {{e|}} (every other
// language) on the "#" line. —
// Only one of these three panels should be open at a time —
// each closes the other two on open (see buildChipField's
// onOpen/close). Safe to reference fields not yet assigned
// below: onOpen only ever runs later, from a click.
var deverField = buildChipField({
wrapCls: "wiktku-dfn-dever",
toggleLabel: "+ Dever/Şîve",
hint: "Bi vê maneyê li kû derê an di kîjan devokê de tê bikaranîn?",
suggestions: DEVER_SUGGESTIONS,
onOpen: function () { etiketField.close(); kategoriField.close(); }
});
block.data("addDeverChip", deverField.addChip);
var etiketField = buildChipField({
wrapCls: "wiktku-dfn-etiket",
toggleLabel: "+ Etîket",
hint: "xisûsen, Mêrdîn, anatomî, Badînî",
onOpen: function () { deverField.close(); kategoriField.close(); }
});
block.data("addEtiketChip", etiketField.addChip);
var kategoriField = buildChipField({
wrapCls: "wiktku-dfn-kategori",
toggleLabel: "+ Kategorî",
hint: "wekî etîket lê xuya nabe tenê kategorîze dike",
onOpen: function () { deverField.close(); etiketField.close(); }
});
block.data("addKategoriChip", kategoriField.addChip);
// Dever/Şîve starts open for ku (where it's the common case)
// and closed otherwise; backs off once the person toggles it
// themselves (see buildChipField's setOpenDefault). Re-synced
// from refresh() whenever lang changes — see block.data below.
function updateDeverOpenState() {
deverField.setOpenDefault((getValues().lang || "") === "ku");
}
block.data("updateDeverOpenState", updateDeverOpenState);
updateDeverOpenState();
// Toggles always sit together in one row; panels render in their
// own area below so opening one never displaces the others.
var chipFieldRow = $("<div class='wiktku-dfn-chipfield-row'></div>")
.append(deverField.wrap).append(etiketField.wrap).append(kategoriField.wrap);
var chipFieldPanels = $("<div class='wiktku-dfn-chipfield-panels'></div>")
.append(deverField.panel).append(etiketField.panel).append(kategoriField.panel);
block.append(tplLink);
block.append(tplRow);
block.append(dfnArea);
block.append(nonKuWrap);
block.append(chipFieldRow);
block.append(chipFieldPanels);
// Apply lang filter on initial build.
updateTplOptions();
updateManeMode();
// — Sub-field groups. —
// The four most-used groups (Nimûne, Jêgirtin, Hevmane, Dijmane)
// stay open; the rest sit behind the "Malûmatên din" toggle
// (adv: true → .wiktku-dfn-subgroup-adv, keyed off by the collapse
// CSS). Etîket/Kategorî used to be here too (adv: true) but are
// now their own chip+toggle fields (see deverField/etiketField/
// kategoriField above) — the same "1c" pattern already keeps
// them out of the way when empty, so they no longer need the
// advanced section either.
// Jêgirtin isn't in this array — it's a compound widget built
// after the loop — but it's inserted right after Nimûne so the
// visible order matches the list above.
var allGroups = [
{ cls: "wiktku-dfn-nimune", label: "Nimûne", rows: 2, narrow: false },
{ cls: "wiktku-dfn-hevmane", label: "Hevmane", rows: 1, narrow: true, izah: "Spehî hevmaneya Bedew e." },
{ cls: "wiktku-dfn-dijmane", label: "Dijmane", rows: 1, narrow: true, izah: "Zengîn dijmaneya feqîr e." },
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv", rows: 1, narrow: true, izah: "Varyantên vê kelîmeyê", adv: true },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne", rows: 1, narrow: true, izah: "Pisîk û kûçik termên koordîne ne çunkî Jornavên wan gelek in", adv: true },
{ cls: "wiktku-dfn-jornav", label: "Jornav", rows: 1, narrow: true, izah: "Heywan jornavê guhandar e", adv: true },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav", rows: 1, narrow: true, izah: "Guhandar jêrnavê heywan e", adv: true }
];
// adv:true groups land inside this box instead of directly in
// block — see the "Malûmatên din" accordion built after
// the loop, which appends advBox right where the old toggle
// used to sit (right after Dijmane, since that's the loop's
// last non-adv, block-appended group).
var advBox = $("<div class='wiktku-sense-adv-box'></div>");
$.each(allGroups, function (_, g) {
var groupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup " + g.cls + "-group");
if (g.adv) groupDiv.addClass("wiktku-dfn-subgroup-adv");
var groupParent = g.adv ? advBox : block;
// Declared once here rather than with `var` in each branch
// below — both branches assign the same pair, just with a
// different lbl and list, so one declaration avoids the
// (harmless but lint-flagged) re-declaration.
var lbl, list;
function buildNimuneLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-nimune-li");
var mainTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-text-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-text")
.attr("placeholder", "Nimûneya emilandina kelîmeyê tu bi xwe binivîse…");
mainTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(list.children("li:last-child"));
if (val && isLast) {
list.append(buildNimuneLi());
}
});
li.append(mainTa);
var subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
if (needsTransliteration(lang)) {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyona bi alfabeya latînî")
);
}
if (lang && lang !== "ku") {
subWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî")
);
}
if (subWrap.children().length) li.append(subWrap);
return li;
}
// The placeholder names the current language, but that's
// often not chosen yet when this block is first built —
// so keep a function around that refresh() (and onSubInput,
// for newly-grown fields) can call again once lang is set
// or changes.
function subPlaceholder() {
if (g.noLangSuffix) return g.label + "…";
var lang = getValues().lang || "";
var langLabel = lang ? getLangLabel(lang) : "";
return g.label + (langLabel ? " bi " + langLabel : "") + "…";
}
function updateSubPlaceholders() {
list.find("> li > textarea.wiktku-dfn-subfield").attr("placeholder", subPlaceholder());
}
if (g.cls === "wiktku-dfn-nimune") {
// Nimûne — compound widget with tr/t sub-fields.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ");
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
groupDiv.data("buildNimuneLi", buildNimuneLi);
list.append(buildNimuneLi());
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
} else {
// Regular always-visible group — label + growing textarea list.
lbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text(g.label))
.append(": ")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-hint").text(g.izah));
var useModifiers = !!MODIFIER_SUBGROUPS[g.cls];
list = $("<ol></ol>").addClass("wiktku-dfn-sublist " + g.cls + "-list");
list.data("updatePlaceholders", updateSubPlaceholders);
var firstInput = $("<textarea></textarea>")
.attr("name", g.cls + "-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", g.rows)
.attr("class", "wiktku-dfn-subfield" + (g.narrow ? " wiktku-start-narrow" : ""))
.attr("placeholder", subPlaceholder());
firstInput.on("input", function () {
steps.dfn.onSubInput(list, this, g.rows, g.narrow, useModifiers);
});
list.append(useModifiers ? makeModifierLi(firstInput) : $("<li></li>").append(firstInput));
groupDiv.append(lbl).append(list);
groupParent.append(groupDiv);
}
});
// — Jêder group — growing list, same structure as Nimûne. —
(function () {
var jederGroupDiv = $("<div></div>").addClass("wiktku-dfn-subgroup wiktku-dfn-jeder-group");
var jederLbl = $("<span></span>").addClass("wiktku-dfn-sublabel")
.append($("<span></span>").addClass("wiktku-dfn-sublabel-term").text("Jêgirtin"))
.append(": ");
var jederList = $("<ol></ol>").addClass("wiktku-dfn-sublist wiktku-dfn-jeder-list");
function buildJederLi() {
var lang = getValues().lang || "";
var li = $("<li></li>").addClass("wiktku-dfn-jeder-li");
// Quote text textarea
var quoteTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote")
.attr("placeholder", "Nimûneya emilandinê ji kitêb an çavkaniyeke din binivîse");
quoteTa.on("input", function () {
highlightLinebreak(this);
var val = $.trim($(this).val());
var isLast = li.is(jederList.children("li:last-child"));
if (val && isLast) {
jederList.append(buildJederLi());
}
});
li.append(quoteTa);
// tr/t sub-fields for the quote — shown as needed for the language.
var quoteSubWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
if (needsTransliteration(lang)) {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…")
);
}
if (lang && lang !== "ku") {
quoteSubWrap.append(
$("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî")
);
}
if (quoteSubWrap.children().length) li.append(quoteSubWrap);
// Citoid auto-fill: fetch citation data from a URL, DOI or ISBN and
// prefill the source şablon + fields below (see Gadget-citoid-helper.js).
var citoidWrap = $("<div></div>").addClass("wiktku-dfn-quote-citoid");
var citoidInput = $("<textarea></textarea>")
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-citoid-input")
.attr("rows", 1)
.attr("placeholder", "URL, DOI an ISBN binivîse û pêl bike bişkoka 'Bi otomatîk tijî bike' da ku otomatîk were tijîkirin…");
var citoidBtn = new OO.ui.ButtonWidget({
label: "Bi otomatîk tijî bike",
classes: [ "wiktku-dfn-quote-citoid-btn", "wiktku-add-sense-btn" ]
});
var citoidStatus = $("<span></span>").addClass("wiktku-dfn-quote-citoid-status");
// Fetches citation data from Citoid and fills this citation's
// şablon + fields — only rebuilds fields on template change,
// and never overwrites values the user already typed.
function runCitoidFetch() {
var query = $.trim(citoidInput.val());
if (!query) {
mw.notify("Ji kerema xwe re pêşî URL, DOI an ISBNyekê binivîse.");
return;
}
citoidBtn.setDisabled(true);
citoidStatus.text("Tê anîn…");
wikt.citoid.fetchJederFields(query).then(function (result) {
var jederKey = result.jederKey;
var hasTpl = !!jederKey && srcSel.find("option[value='" + jederKey + "']").length > 0;
var targetVal = hasTpl ? jederKey : "__manual__";
// Only rebuild if switching şablon, so a repeat fetch
// doesn't clear fields already filled in by hand.
if (srcSel.val() !== targetVal) {
srcSel.val(targetVal).trigger("change");
}
if (hasTpl) {
// Tenê qadên vala tijî bike.
$.each(result.fields, function (key, val) {
var input = srcFields.find("[data-field-key='" + key + "']");
if (input.length && !$.trim(input.val())) {
input.val(val);
}
});
} else {
// Şablonek nehat naskirin ji bo vê itemType — rêzikeke
// çavkaniyê ya bi destan a ji qadên Citoid tê çêkirin,
// lê tenê ger qada destan vala be.
var manualField = srcFields.find(".wiktku-dfn-quote-src-manual");
if (!$.trim(manualField.val())) {
var parts = [];
$.each(["nivîskar", "sernav", "websîte", "kovar", "weşanxane", "sal", "url"], function (_, k) {
if (result.fields[k]) parts.push(result.fields[k]);
});
manualField.val(parts.join(", "));
}
}
citoidStatus.text("");
}, function (errorMsg) {
citoidStatus.text(typeof errorMsg === "string" ? errorMsg : "Nehat dîtin. Kontrol bike ka URL/DOI/ISBN rast e.");
}).always(function () {
citoidBtn.setDisabled(false);
});
}
citoidBtn.on("click", runCitoidFetch);
citoidInput.on("keydown", function (e) {
if (e.which === 13) { // Enter
e.preventDefault();
runCitoidFetch();
}
});
citoidWrap.append(citoidInput).append(citoidBtn.$element).append(citoidStatus);
// Source template selector, specific to this citation.
var srcSel = $("<select></select>").addClass("wiktku-dfn-quote-src-sel wiktku-plain-select");
srcSel.append($("<option value=''>— Çavkaniyê hilbijêre —</option>"));
// Hidden from the dropdown, but left in the DOM so Citoid fetch, the category picker and draft-restore can still select it via .val().
srcSel.append($("<option></option>").val("__manual__").text("Bi destê xwe binivîsim").attr("hidden", true));
srcSel.append($("<option></option>").val("__category__").text("Ji Kategorî:Çavkanî şablonekê hilbijêre"));
var jederDefs = steps.dfn.jederDefs || {};
$.each(jederDefs, function (tplName, def) {
srcSel.append($("<option></option>").val(tplName).text(def.label));
});
// Dynamic source fields container.
var srcFields = $("<div></div>").addClass("wiktku-dfn-quote-src-fields");
srcSel.on("change", function () {
srcFields.empty();
var chosen = $(this).val();
// Citoid is only useful before a source is chosen —
// hide it once a selection is made, restore on reset.
if (chosen) {
citoidWrap.hide();
} else {
citoidWrap.show();
return;
}
if (chosen === "__manual__") {
// Freeform source, written by the user — becomes the 2nd
// positional param of {{jêder|lang|…}} instead of çavkanî?.
srcFields.append(
$("<textarea></textarea>")
.attr("rows", 4)
.addClass("wiktku-dfn-subfield wiktku-dfn-quote-src-manual")
.attr("placeholder", "Çavkaniyê bi destê xwe binivîse…")
);
return;
}
if (chosen === "__category__") {
// Search widget over [[Kategorî:Çavkanî]] members; picking one switches the row into the hidden manual mode with {{Şablon}} pre-filled.
var catWrap = $("<span></span>").addClass("wiktku-native-select-wrap wiktku-dfn-quote-src-cat");
var catInput = $("<input type='search' autocomplete='off'>")
.addClass("wiktku-start-narrow wiktku-dfn-quote-src-cat-input")
.attr("placeholder", "Li navê şablonê bigere…");
var catResults = $("<ul></ul>").addClass("wiktku-ac-results wiktku-dfn-quote-src-cat-results").hide();
var catStatus = $("<span></span>").addClass("wiktku-dfn-quote-src-cat-status").text("Şablon tên barkirin…");
var showCatResults = function (query) {
var q = $.trim(query).toLowerCase();
catResults.empty();
var list = catSrcTemplates || [];
var matched = q ?
$.grep(list, function (name) { return name.toLowerCase().indexOf(q) !== -1; }) :
list;
if (!matched.length) {
catResults.hide();
return;
}
$.each(matched.slice(0, 50), function (_, name) {
var li = $("<li></li>").text(name).attr("data-value", name);
var touchMoved = false;
li.on("touchstart", function () { touchMoved = false; });
li.on("touchmove", function () { touchMoved = true; });
li.on("touchend", function (e) {
if (touchMoved) return;
e.preventDefault();
selectCatTpl(name);
});
li.on("mousedown", function (e) {
e.preventDefault();
selectCatTpl(name);
});
catResults.append(li);
});
catResults.show();
};
// Wraps the chosen şablon as {{Name}}, drops it into the
// manual textarea, and switches the row into manual mode
// so the user can keep editing it (add params, etc.).
var selectCatTpl = function (name) {
catResults.hide();
srcSel.val("__manual__");
srcSel.trigger("change");
srcFields.find(".wiktku-dfn-quote-src-manual")
.val("{{" + name + "}}")
.trigger("focus");
};
catInput.on("input", function () {
showCatResults($(this).val());
});
catInput.on("focus", function () {
showCatResults($(this).val());
});
catInput.on("blur", function () {
setTimeout(function () { catResults.hide(); }, 200);
});
catWrap.append(catInput).append(catResults).append(catStatus);
srcFields.append(catWrap);
fetchCatSrcTemplates().then(function () {
catStatus.text("");
}, function () {
catStatus.text("Şablon nehatin barkirin.");
});
return;
}
var def = (steps.dfn.jederDefs || {})[chosen];
if (!def) return;
// Wide/narrow/medium layout, same field shape as
// MediaWiki:Gadget-wikt.add-examples.js: sernav-type
// fields take a full-width multiline box, short ones
// like sal/rûpel/isbn stay narrow, everything else is
// a plain single-line medium field.
$.each(def.fields, function (_, field) {
var row = $("<div></div>")
.addClass("wiktku-dfn-quote-src-row wiktku-dfn-quote-src-row--" + (field.width || "medium"));
var fieldLbl = $("<label></label>").text(field.label + ": ");
var inp = $(field.multiline ? "<textarea></textarea>" : "<input type='text'>")
.addClass("wiktku-dfn-quote-src-input")
.attr("data-field-key", field.key)
.attr("placeholder", field.label + "…");
if (field.multiline) {
inp.attr("rows", 2).addClass("wiktku-dfn-quote-src-input--title");
}
row.append(fieldLbl).append(inp);
srcFields.append(row);
});
});
li.append(citoidWrap).append(srcSel).append(srcFields);
return li;
}
jederGroupDiv.data("buildJederLi", buildJederLi);
jederList.append(buildJederLi());
jederGroupDiv.append(jederLbl).append(jederList);
// Straight after Nimûne rather than last: Jêgirtin is the
// same kind of thing (an attested use of the word) and the
// two belong together — see the allGroups comment above.
var nimuneGroup = block.find(".wiktku-dfn-nimune-group");
if (nimuneGroup.length) jederGroupDiv.insertAfter(nimuneGroup);
else block.append(jederGroupDiv);
}());
// Apply the initial hide/show state for the sub-groups (matches
// tplSelect's starting value, which is normally empty).
updateFieldVisibility();
// ── "Malûmatên din" — accordion, per sense, after
// Dijmane. Fieldset-style: advBox (built before the allGroups
// loop above, which appends every adv:true group's div into
// it instead of straight into block) gets a legend that
// floats on its top border — diamond + label + "?" help +
// chevron — plus a one-line summary shown only while
// collapsed, so a glance says whether anything's back there
// without opening it. One toggle per sense rather than one
// for the whole step: the fields behind it (Formên
// alternatîv, Jornav…) belong to a single meaning, so a
// global switch flipped them for every sense at once and gave
// a count that answered "across all senses" — not a question
// anybody was asking.
(function () {
block.addClass("wiktku-sense-adv-hidden");
advBox.addClass("wiktku-sense-adv-box");
var advFields = [
{ cls: "wiktku-dfn-alternativ", label: "Formên alternatîv" },
{ cls: "wiktku-dfn-koordine", label: "Termên koordîne" },
{ cls: "wiktku-dfn-jornav", label: "Jornav" },
{ cls: "wiktku-dfn-jernav", label: "Jêrnav" }
];
var legend = $("<div class='wiktku-sense-adv-legend'></div>");
legend.append($("<span class='wiktku-sense-adv-diamond'>◆</span>"));
legend.append($("<span class='wiktku-sense-adv-label'>Malûmatên din</span>"));
var chevron = $("<button type='button' class='wiktku-sense-adv-chevron' title='Veke/bigire'></button>")
.attr("aria-expanded", "false");
legend.append(chevron);
var summary = $("<div class='wiktku-sense-adv-summary'></div>");
var summaryText = $("<span class='wiktku-sense-adv-summary-text'></span>");
summary.append($("<span class='wiktku-sense-adv-summary-dots'>⋯</span>"))
.append(summaryText);
function syncAdv() {
var hidden = block.hasClass("wiktku-sense-adv-hidden");
chevron.text(hidden ? "▾" : "▲").attr("aria-expanded", hidden ? "false" : "true");
advBox.toggleClass("wiktku-sense-adv-open", !hidden);
var filledLabels = [];
$.each(advFields, function (_, f) {
var any = block.find("." + f.cls + "-list textarea").filter(function () {
return $.trim($(this).val()) !== "";
}).length > 0;
if (any) filledLabels.push(f.label);
});
summaryText.text(filledLabels.length ? filledLabels.join(", ") : "Tiştek nehatiye dagirtin");
}
chevron.on("click", function (e) {
e.preventDefault();
block.toggleClass("wiktku-sense-adv-hidden");
syncAdv();
});
block.data("syncSenseAdv", syncAdv);
advBox.prepend(summary).prepend(legend);
// After the last always-visible group (Dijmane) and before
// the advanced ones (now inside advBox), which sit below
// it in DOM order.
var lastPrimary = block.find(".wiktku-dfn-dijmane-group");
if (lastPrimary.length) advBox.insertAfter(lastPrimary);
else block.append(advBox);
syncAdv();
}());
return block;
},
/** Shows only `block` among the sense list; hides every other one. */
setOpenSense: function (stepDiv, block) {
stepDiv.find("li.wiktku-dfn-sense").each(function () {
$(this).toggleClass("wiktku-sense-collapsed", !$(this).is(block));
});
steps.dfn.renderSenseTabs(stepDiv);
},
/** Rebuilds the "Mane 1 / Mane 2 / [trash] / + Maneyeke din" tab row from the current sense list. */
renderSenseTabs: function (stepDiv) {
var tabsDiv = stepDiv.find(".wiktku-dfn-sense-tabs");
if (!tabsDiv.length) return;
tabsDiv.empty();
var blocks = stepDiv.find("li.wiktku-dfn-sense");
var openLi = null;
blocks.each(function (i) {
var li = $(this);
var isOpen = !li.hasClass("wiktku-sense-collapsed");
if (isOpen) openLi = li;
var tab = $("<span class='wiktku-dfn-sense-tab'></span>")
.text("Mane " + (i + 1))
.toggleClass("wiktku-tab-active", isOpen);
tab.on("click", function () { steps.dfn.setOpenSense(stepDiv, li); });
tabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — so
// there's only ever a single, deliberate hit-target for a
// destructive action, positioned away from the tabs people
// click to switch senses. Always keep at least one Mane.
if (blocks.length > 1 && openLi) {
var openIdx = blocks.index(openLi);
var trash = makeTrashButton("Vê maneyê jê bibe (Mane " + (openIdx + 1) + ")");
trash.on("click", function () {
if (!window.confirm("Tu dixwazî Mane " + (openIdx + 1) + " jê bibî? Ev tişt nayê vegerandin.")) return;
steps.dfn.removeSense(stepDiv, openLi);
});
tabsDiv.append(trash.$element);
}
var addBtn = $("<button type='button' class='wiktku-add-sense'></button>")
.append($("<span class='wiktku-add-sense-plus'>+</span>"))
.append(document.createTextNode("Maneyeke din"));
addBtn.on("click", function (e) {
e.preventDefault();
steps.dfn.addSense(stepDiv);
});
tabsDiv.append(addBtn);
},
/** Appends a sense, opens it, and hides the others. */
addSense: function (stepDiv) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var newBlock = steps.dfn.buildSenseBlock(stepDiv);
list.append(newBlock);
initSelects(newBlock);
steps.dfn.setOpenSense(stepDiv, newBlock);
newBlock.find("textarea.wiktku-dfn-mane").first().focus();
refresh();
return newBlock;
},
/** Removes one sense block. Assumes the caller already confirmed
* and already checked more than one sense remains. If the
* removed sense was the open one, opens whichever sense ends up
* taking its place (or the last one, if it was the last). */
removeSense: function (stepDiv, li) {
var list = stepDiv.find("ol.wiktku-dfn-list");
var blocks = list.find("li.wiktku-dfn-sense");
var removedIdx = blocks.index(li);
var wasOpen = !li.hasClass("wiktku-sense-collapsed");
li.remove();
if (wasOpen) {
var remaining = list.find("li.wiktku-dfn-sense");
var next = remaining.eq(removedIdx);
if (!next.length) next = remaining.last();
if (next.length) steps.dfn.setOpenSense(stepDiv, next);
}
steps.dfn.renderSenseTabs(stepDiv);
refresh();
},
/** Grows a sub-list (nimûne/formên alternatîv/hevmane/dijmane/termên koordîne) on input. */
onSubInput: function (list, input, rows, narrow, modifiers) {
var val = $.trim($(input).val());
var item = $(input).parent();
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
var next = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-sub-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", rows)
.attr("class", "wiktku-dfn-subfield" + (narrow ? " wiktku-start-narrow" : ""));
next.on("input", function () {
steps.dfn.onSubInput(list, this, rows, narrow, modifiers);
});
list.append(modifiers ? makeModifierLi(next) : $("<li></li>").append(next));
// New field starts with no placeholder — give it (and any
// others in the list) the current one.
var updatePlaceholders = list.data("updatePlaceholders");
if (typeof updatePlaceholders === "function") updatePlaceholders();
}
},
// Populated from [[MediaWiki:Gadget-start.js/data.json]] (tplDefs, jederDefs).
tplDefs: {},
jederDefs: {},
build: function (stepDiv) {
if (stepDiv.find("ol.wiktku-dfn-list").length) return null;
var tabsDiv = $("<div class='wiktku-dfn-sense-tabs'></div>");
stepDiv.append(tabsDiv);
var list = $("<ol class='wiktku-dfn-list'></ol>");
list.append(steps.dfn.buildSenseBlock(stepDiv));
stepDiv.append(list);
steps.dfn.renderSenseTabs(stepDiv);
return null;
},
/** Reads all sense blocks from the DOM and returns an array of sense objects. */
getSenses: function (stepDiv) {
var senses = [];
stepDiv.find("li.wiktku-dfn-sense").each(function () {
var block = $(this);
var tplSel = block.find("select.wiktku-dfn-tpl-select");
var tpl = tplSel.val() || "";
// noLang: template already encodes the language — don't add |lang| param.
var tplNoLang = tpl
? tplSel.find("option[value='" + tpl + "']").attr("data-nolang") === "1"
: false;
var tplTitle = $.trim(block.find("input.wiktku-dfn-tpl-title").val());
var mane = $.trim(block.find("textarea.wiktku-dfn-mane").val());
function readList(cls) {
var vals = [];
// Matches only the canonical field, not the visible word-input mirror makeModifierLi() adds alongside it.
block.find("." + cls + "-list textarea.wiktku-dfn-subfield").each(function () {
var v = $.trim($(this).val());
if (v) vals.push(v);
});
return vals;
}
// Nimûne items are compound: { text, tr?, t? }
function readNimuneList() {
var items = [];
block.find(".wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var text = $.trim($(this).find(".wiktku-dfn-nimune-text").val());
if (!text) return;
var obj = { text: text };
var tr = $.trim($(this).find(".wiktku-dfn-nimune-tr").val());
var t = $.trim($(this).find(".wiktku-dfn-nimune-t").val());
if (tr) obj.tr = tr;
if (t) obj.t = t;
items.push(obj);
});
return items;
}
// Jêder is a growing list of citations, one per li — mirrors readNimuneList().
function readJederList() {
var items = [];
block.find(".wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
var quote = $.trim(li.find(".wiktku-dfn-nimune-quote").val());
if (!quote) return;
var obj = { quote: quote };
var qtr = $.trim(li.find(".wiktku-dfn-nimune-quote-tr").val());
var qt = $.trim(li.find(".wiktku-dfn-nimune-quote-t").val());
if (qtr) obj.quoteTr = qtr;
if (qt) obj.quoteT = qt;
var tplName = li.find(".wiktku-dfn-quote-src-sel").val();
if (tplName === "__manual__") {
var manual = $.trim(li.find(".wiktku-dfn-quote-src-manual").val());
if (manual) obj.jederManual = manual;
} else if (tplName === "__category__") {
// Reachable only if the user opened the category search but
// never picked a şablon — picking one switches the row to
// __manual__ (see selectCatTpl above), so there's nothing
// meaningful to save here.
} else if (tplName) {
obj.jederTpl = tplName;
var fields = {};
li.find(".wiktku-dfn-quote-src-input").each(function () {
var k = $(this).attr("data-field-key");
var v = $.trim($(this).val());
if (k && v) fields[k] = v;
});
obj.jêderFields = fields;
}
items.push(obj);
});
return items;
}
// Dever/Şîve, Etîket and Kategorî are chip fields (buildChipField),
// not textareas, so readList() doesn't reach them — each chip's
// value lives in data-value, scoped by the field's wrap class.
function readChipList(wrapCls) {
var vals = [];
block.find("." + wrapCls + "-wrap .wiktku-dfn-chipfield-chip").each(function () {
var v = $.trim($(this).attr("data-value") || "");
if (v) vals.push(v);
});
return vals;
}
senses.push({
tpl: tpl,
tplNoLang: tplNoLang,
tplTitle: tplTitle,
mane: mane,
etiket: readChipList("wiktku-dfn-etiket"),
kategori: readChipList("wiktku-dfn-kategori"),
nimune: readNimuneList(),
jeder: readJederList(),
alternativ: readList("wiktku-dfn-alternativ"),
hevmane: readList("wiktku-dfn-hevmane"),
dijmane: readList("wiktku-dfn-dijmane"),
koordine: readList("wiktku-dfn-koordine"),
jornav: readList("wiktku-dfn-jornav"),
jernav: readList("wiktku-dfn-jernav"),
dever: readChipList("wiktku-dfn-dever")
});
});
return senses;
},
getOutput: function (values, callback) {
var stepDiv = $("#wiktku-start-dfn");
var senses = steps.dfn.getSenses(stepDiv);
var lang = values.lang;
// A sense counts as "filled" if it has a mane, a chosen template + title,
// or any other content (etîket/hevmane/nimûne/jêder…) worth keeping.
var hasMane = senses.some(function (s) {
return s.mane !== "" || (s.tpl && s.tplTitle) || senseHasExtraContent(s);
});
var defLines = []; // one entry per kept sense (goes to linkify)
var citeLines = []; // parallel — nimûne+jêder for that same sense
var izahLines = []; // parallel — raw îzah gloss for that same sense
// True only once some sense's *actual* dfn text (post tercime/îzah
// split) is non-empty.
var hasLinkableMane = false;
$.each(senses, function (_, sense) {
// Declared once here rather than with `var` in each branch
// below — the template-sense branch returns early, so only
// one of the two assignments ever actually runs, but both
// are lexically in this same function scope.
var idx, cite;
// — Template sense (e.g. {{guharto|ku|somepage}}) —
if (sense.tpl) {
if (!sense.tplTitle) return; // skip empty template senses
var tplKatTpl = buildSenseTpl(sense, "kategori", lang, "K");
var tplDeverTpl = buildDeverTpl(sense, lang);
var tplLabelTpl = buildEtiketTpl(sense, lang);
var tplCall = sense.tplNoLang
? "{{" + sense.tpl + "|" + sense.tplTitle + "}}"
: "{{" + sense.tpl + "|" + lang + "|" + sense.tplTitle + "}}";
idx = defLines.length;
cite = buildCite(sense, lang);
defLines.push("# " + (tplKatTpl ? tplKatTpl + " " : "") + (tplDeverTpl ? tplDeverTpl + " " : "") +
(tplLabelTpl ? tplLabelTpl + " " : "") + tplCall +
(cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(""); // template senses have no îzah
return;
}
// — Normal mane sense —
var dfn = sense.mane;
if (!dfn && !senseHasExtraContent(sense)) return; // truly empty
// Pull the îzah gloss (if any) out of the combined tercime+îzah
// string — it's spliced back in separately below, after its
// own linkify pass (see izahLines / the second linkifyText call).
var izahRaw = null;
var tercimeIzah = splitTercimeIzah(dfn);
if (tercimeIzah) {
dfn = tercimeIzah.tercime;
izahRaw = stripWikilinks(tercimeIzah.izah);
}
if (dfn) {
dfn = stripWikilinks(dfn);
// Sentence-case + trailing period are for ku definitions,
// which are written as full glosses.
if (lang === "ku") {
if (dfn[0] !== dfn[0].toUpperCase()) dfn = capitalize(dfn);
if (!dfn.match(/[.…?!)\]]$/)) dfn += ".";
}
// Real prose — this is what makes the sense worth running
// through the main linkify pass.
hasLinkableMane = true;
} else if (izahRaw === null) {
// No mane at all — this is just the "{{mane?|lang}}"
// placeholder, a bare template call with no plain words
// in it. Do NOT count it as linkable: sending nothing but
// bracketed templates through the main linkify pass hangs
// the linkify gadget (see the hasLinkableMane/hasIzah
// check below).
dfn = lang === "ku" ? "{{mane?|" + lang + "}}" : "";
}
var katTpl = buildSenseTpl(sense, "kategori", lang, "K");
var deverTpl = buildDeverTpl(sense, lang);
var labelTpl = buildEtiketTpl(sense, lang);
var out = "# " + (katTpl ? katTpl + " " : "") + (deverTpl ? deverTpl + " " : "") + (labelTpl ? labelTpl + " " : "") + dfn;
idx = defLines.length;
var hasThisIzah = izahRaw !== null && $.trim(izahRaw) !== "";
// Bare {{...}} token — like {{wiktku-cite-N}}, this survives
// linkify's first pass untouched (it treats template calls as
// opaque), then gets swapped for {{îzah|…}} once the actual
// gloss text has been linkified in its own separate pass.
if (hasThisIzah) {
out += (dfn ? " " : "") + "{{wiktku-izah-" + idx + "}}";
}
function wordLine(tpl, words) {
if (!words.length) return "";
return "\n#: {{" + tpl + "|" + lang + "|" + words.join("|") + "}}";
}
out += wordLine("alt-r", sense.alternativ);
out += wordLine("hevmane", sense.hevmane);
out += wordLine("dijmane", sense.dijmane);
out += wordLine("koordîne", sense.koordine);
out += wordLine("jornav", sense.jornav);
out += wordLine("jêrnav", sense.jernav);
cite = buildCite(sense, lang);
defLines.push(out + (cite ? "{{wiktku-cite-" + idx + "}}" : ""));
citeLines.push(cite);
izahLines.push(hasThisIzah ? izahRaw : "");
});
// Splice citeLines back into a linkified string, replacing each
// {{wiktku-cite-N}} token with its stored nimûne/jêder block.
function restoreCites(t) {
return t.replace(/\{\{wiktku-cite-(\d+)\}\}/g, function (_, n) {
return citeLines[parseInt(n, 10)] || "";
});
}
// Runs text through the linkify gadget if it's loaded, otherwise
// just passes it through unchanged. Shared by both linkify passes
// below (main mane text, and — separately — îzah glosses).
function linkifyText(t, cb) {
if (typeof $.kuLinkify === "function") {
$.kuLinkify(t, cb);
} else {
cb(t);
}
}
var hasIzah = izahLines.some(function (t) { return t && $.trim(t) !== ""; });
var text = defLines.join("\n") + "\n";
if (!hasMane) {
// No definitions entered — emit {{mane?}} with nimûne?.
text = "# {{mane?|" + lang + "}}\n#* {{nimûne?|" + lang + "}}\n";
callback(text);
return;
}
// Runs the îzah-only linkify pass against `base` (either the raw
// text, when there's no linkable mane to run the main pass on
// first, or the already-linkified main text). Shared by both
// branches below so the token-splice logic only lives once.
function runIzahPass(base) {
var izahJoined = $.map(izahLines, function (raw, idx) {
if (!raw) return undefined;
return "{{wiktku-izah-open-" + idx + "}}" + raw + "{{wiktku-izah-close-" + idx + "}}";
}).join("\n");
linkifyText(izahJoined, function (izahLinked) {
var izahByIdx = {};
var re = /\{\{wiktku-izah-open-(\d+)\}\}([\s\S]*?)\{\{wiktku-izah-close-\1\}\}/g;
var m;
while ((m = re.exec(izahLinked)) !== null) {
izahByIdx[parseInt(m[1], 10)] = m[2];
}
var withIzah = base.replace(/\{\{wiktku-izah-(\d+)\}\}/g, function (_, n) {
var linked = izahByIdx[parseInt(n, 10)];
return linked !== undefined ? "{{îzah|" + linked + "}}" : "";
});
callback(restoreCites(withIzah));
});
}
if (!hasLinkableMane) {
if (!hasIzah) {
// All filled senses are template senses (or otherwise have
// no plain text at all) — nothing here needs linkify.
callback(restoreCites(text));
return;
}
// There's no plain mane text anywhere (e.g. tercime left blank
// with only îzah filled in), so every defLine is just a bare
// template call with nothing to word-link. Sending a line
// like "# {{wiktku-izah-0}}" through the main linkify pass
// gives it no plain words to work with, makes the linkify
// gadget hang — so skip that pass entirely and run only
// the îzah pass, splicing straight into the unlinkified text.
var loadIzahOnly = function () { runIzahPass(text); };
mw.loader.using("ext.gadget.linkify", loadIzahOnly, loadIzahOnly);
return;
}
var didTryToLoad = function () {
// First pass: the main definition text (mane), same as before.
linkifyText(text, function (mainLinked) {
if (!hasIzah) {
callback(restoreCites(mainLinked));
return;
}
// Second pass: îzah glosses, linkified via their own
// separate call. Each gloss is wrapped in its own
// {{wiktku-izah-open/close-N}} pair; like the existing
// {{wiktku-cite-N}} tokens, these sit inside double
// braces so linkify leaves the tokens themselves alone —
// only the raw gloss text between each pair gets linked.
runIzahPass(mainLinked);
});
};
mw.loader.using("ext.gadget.linkify", didTryToLoad, didTryToLoad);
}
},
tewandin: {
id: "tewandin",
name: "tewandin",
question_fmt: 'Tewandina "' + pageName + '" binivîse',
visible: function (values) {
return values.lang && values.pos;
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-tewandin-wrap").length) return null;
var wrap = $("<div class='wiktku-tewandin-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-tewandin-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-tewandin-text")
.attr("rows", 4);
wrap.append(ta);
stepDiv.append(wrap);
function autoPopulate() {
if (!ta.length) return;
var vals = getValues();
var c = vals.cinsiyet || "";
var p = vals.pos || "";
if (!pageName.includes(" ") && c && GENDER_BASE_LABELS[c] &&
(p === "navdêr" || p === "serenav") && vals.lang === "ku" &&
c !== "mndudil") {
var label = (c === "mnmane") ? "mê û nêr" : GENDER_BASE_LABELS[c];
var out = "{{ku-tewîn-nav|" + label;
if (p === "serenav") out += "|y";
out += "}}";
if (!$.trim(ta.val())) ta.val(out);
}
}
// The example placeholder only fits Kurmancî nouns — showing it
// for a verb or another language would suggest the wrong template.
function updatePlaceholder() {
if (!ta.length) return;
var vals = getValues();
ta.attr("placeholder", (vals.lang === "ku" && vals.pos === "navdêr") ? "{{ku-tewîn-nav|nêr}}" : "");
}
setTimeout(function () {
autoPopulate();
updatePlaceholder();
$("#wiktku-start-form").on("change.tewandin",
"#wiktku-start-lang-field, #wiktku-start-cinsiyet-field, #wiktku-start-pos-field",
function () { autoPopulate(); updatePlaceholder(); });
}, 0);
return ta;
},
output: function () {
var manualText = $.trim($("#wiktku-start-tewandin .wiktku-tewandin-text").val());
if (manualText) {
return "\n==== Tewandin ====\n" + manualText + "\n";
}
return "";
}
},
// resim — one or more images
resim: {
id: "resim",
name: "resim",
question_fmt: "Resmekê lê zêde bike",
visible: function (values) { return !!(values.lang && values.pos); },
/** Builds one image row: filename + caption + alt text. */
buildItem: function (stepDiv) {
var item = $("<li class='wiktku-resim-item'></li>");
var fileRow = $("<div class='wiktku-posparams-row'></div>");
var fileInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-file")
.attr("placeholder", "navê_dosyayê.jpg");
fileRow.append($("<label></label>").text("Navê dosyayê: ")).append(fileInp);
var capRow = $("<div class='wiktku-posparams-row'></div>");
var capInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-caption")
.attr("placeholder", "Danasîna resimê…");
capRow.append($("<label></label>").text("Danasîn: ")).append(capInp);
// Alt text: what a screen reader announces for this image
var altRow = $("<div class='wiktku-posparams-row'></div>");
var altInp = $("<input type='text'>")
.addClass("wiktku-start-step-field wiktku-start-narrow wiktku-resim-alt")
.attr("placeholder", "Ji bo kesên nabînin, çi tê dîtin? (ne mecbûrî)");
altRow.append($("<label></label>").text("Nivîsa alternatîf: ")).append(altInp);
// Auto-fill the caption with the plain page name when a filename
// is typed
fileInp.on("input", function () {
var file = $.trim($(this).val());
var curCap = $.trim(capInp.val());
var isDefault = !curCap || curCap === pageName;
if (file && isDefault) {
capInp.val(pageName);
} else if (!file && isDefault) {
capInp.val("");
}
steps.resim.onFileInput(stepDiv, item, this);
});
item.append(fileRow).append(capRow).append(altRow);
return item;
},
/** Grows the image list when the last row's filename gets content. */
onFileInput: function (stepDiv, item, input) {
var val = $.trim($(input).val());
var list = stepDiv.find("ol.wiktku-resim-list");
var isLast = item.is(list.children("li:last-child"));
if (val && isLast) {
list.append(steps.resim.buildItem(stepDiv));
}
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-resim-wrap").length) return null;
var wrap = $("<div class='wiktku-resim-wrap'></div>");
var list = $("<ol class='wiktku-resim-list'></ol>");
list.append(steps.resim.buildItem(stepDiv));
wrap.append(list);
stepDiv.append(wrap);
return null;
},
/** Reads every filled row from the DOM into an array of {file, caption, alt}
* objects — blank rows (missing filename or caption) are skipped.
* Non-Kurdish captions get wrapped in {{zim|lang|…}} here, unless
* the user already typed their own {{zim|…}} template. Alt text is
* optional and left as plain text either way. */
getImages: function (values) {
var lang = (values && values.lang) || getValues().lang || "";
var images = [];
$("#wiktku-start-resim li.wiktku-resim-item").each(function () {
var file = $.trim($(this).find(".wiktku-resim-file").val());
var caption = $.trim($(this).find(".wiktku-resim-caption").val());
var alt = $.trim($(this).find(".wiktku-resim-alt").val());
if (!file || !caption) return;
// Strip leading "Wêne:" or "File:" if the user typed it — we add it ourselves.
file = file.replace(/^(wêne|file|image)\s*:\s*/i, "");
if (lang && lang !== "ku" && !/^\{\{zim\s*\|/i.test(caption)) {
caption = "{{zim|" + lang + "|" + caption + "}}";
}
images.push({ file: file, caption: caption, alt: alt });
});
return images;
},
output: function (values) {
var images = steps.resim.getImages(values);
if (!images.length) return "";
return images.map(function (img) {
var altPart = img.alt ? "|alt=" + img.alt : "";
return "[[Wêne:" + img.file + "|thumb" + altPart + "|" + img.caption + "]]";
}).join("\n") + "\n";
}
},
// h-bnr — {{h-bnr|Page1|Page2|…}} prepended before the lang section (shared, not per-section)
bnrh: {
id: "bnrh",
name: "herwiha binêre (h-bnr)",
question_fmt: "Şablona h-bnr li qismê herî jor a rûpelê",
//visible: function (values) { return !!values.lang; },
multiple: true,
narrow: true,
placeholder: "Ji bo 'test' bnr 'Test', 'teşt'",
output: function (values) {
var pages = $.grep(values.bnrh || [], function (p) { return !!$.trim(p); });
if (!pages.length) return "";
return "{{h-bnr|" + pages.map(function (p) { return $.trim(p); }).join("|") + "}}\n";
}
},
bikaranin: {
id: "bikaranin",
name: "Notên bikaranînê",
question_fmt: "Notên bikaranînê yên li ser vê kelîmeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
output: function (values) {
var text = $.trim(values.bikaranin || "");
if (!text) return "";
return "\n==== Notên bikaranînê ====\n" + text + "\n";
},
},
xwendin: {
id: "xwendin",
name: "Bêhtir bixwîne",
question_fmt: "Lînkên derveyî eleqedarî vê madeyê",
visible: function (values) {
return values.lang && values.pos;
},
multiple: false,
narrow: false,
placeholder: function (values) {
if (!values || !values.lang) {
return `* {{wpedî}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
}
const ziman = values.lang === "ku" ? "" : `|ziman=${values.lang}`;
return `* {{wpedî${ziman}}} ji bo ku meqaleya Wîkîpediyayê lînk bidî`;
},
output: function (values) {
var text = $.trim(values.xwendin || "");
if (!text) return "";
return "\n==== Bêhtir bixwîne ====\n" + text + "\n";
},
},
alfabeyendin: {
id: "alfabeyendin",
name: "Bi alfabeyên din",
question_fmt: function (values) {
var base = "Nivîsîna vê kelîmeyê bi alfabeyên din"
var langLabel = values.lang ? getLangLabel(values.lang) : "";
if (langLabel) {
return base + " ên " + langLabel;
}
return base;
},
// Latin-script Kurdish itself, and languages with only one script
// on record, don't need this — but any language metadata.json
// lists more than one script for (data.json's "sc") clearly does.
visible: function (values) {
if (!(values.lang && values.pos)) return false;
var lang = values.lang;
return lang === "ku" || ZARAVA_LANG_CODES.indexOf(lang) !== -1 || langHasMultipleScripts(lang);
},
multiple: false,
narrow: false,
placeholder: "Ji bo kurmancî: * {{ku-ar|şiklê alfaba erebî}}",
output: function (values) {
var text = $.trim(values.alfabeyendin || "");
if (!text) return "";
return "\n==== Bi alfabeyên din ====\n" + text + "\n";
},
},
formalt: makeWordListStep("formalt", "formên alternatîv", "Formên alternatîv ên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
hevmane: makeWordListStep("hevmane", "hevmane", "Hevmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
dijmane: makeWordListStep("dijmane", "dijmane", "Dijmaneyên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jornav: makeWordListStep("jornav", "jornav", "Jornavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
jernav: makeWordListStep("jernav", "jêrnav", "Jêrnavên vê kelîmeyê ji bo hemû maneyan eger te li binê maneyê nenivîsandibe"),
je: makeWordListStep("je", "jê", "Kelîmeyên jê çêbûyî binivîse"),
tekildar: makeWordListStep("tekildar", "têkildar", "Kelîmeyên ji aliyê etîmolojiyê ve têkildarî vê kelîmeyê binivîse"),
binereherwiha: makeWordListStep("binereherwiha", "herwiha binêre", "Kelîmeyên din ên eleqedar binivîse"),
etym: {
id: "etym",
name: "etîmolojî",
question_fmt: "Etîmolojiya kelîmeyê binivîse",
// etymTpls populated from data.json
etymTpls: {},
visible: function (values) {
return values.lang && values.pos;
},
// Fills the insert-dropdown from the current etymTpls object.
refreshTplSelect: function () {
var sel = $("#wiktku-start-etym .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
markTemplateOptions(sel, function (opt) { return tplNameFromSnippet(opt.val()); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-etym-wrap").length) return null;
var wrap = $("<div class='wiktku-etym-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-etym-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-etym-text")
.attr("rows", 4)
.attr("placeholder", "Ji {{ji|en|ku|apple}}. yan rasterast binivîse…");
// Template insert dropdown — options also refreshed after data.json loads.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
$.each(steps.etym.etymTpls, function (name, tpl) {
sel.append($("<option></option>").val(tpl.snippet).text(name + " — " + tpl.desc));
});
sel.on("change", function () {
var snippet = $(this).val();
if (!snippet) return;
var lang = getValues().lang || "";
snippet = snippet.replace(/CODE/g, lang).replace(/SOURCELANG/g, "?");
insertAtCursor(ta[0], snippet);
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(ta);
stepDiv.append(wrap);
initSelects(wrap);
return ta;
},
output: function (values) {
var text = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
if (text) {
return "\n==== Etîmolojî ====\n" + text + "\n";
}
// Multi-word page names skip the {{ji?|lang}} placeholder entirely -- "source unknown?" doesn't apply to a phrase.
if (pageName.includes(" ")) return "";
// No etymology entered — emit {{ji?|lang}} as a placeholder.
// Skip it only when every filled sense is a şablon sense
// a completely empty entry with no şablon chosen still gets the placeholder
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasFilledTplSense = dfnSenses.some(function (s) {
return !!s.tpl && !!s.tplTitle;
});
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
if (hasFilledTplSense && !hasRegularMane) return "";
return "\n==== Etîmolojî ====\n{{ji?|" + values.lang + "}}\n";
} },
dunde: {
id: "dunde",
name: "dûnde",
question_fmt: "Eger ev kelîme ji vî zimanî ketibe zimanekî din, vî zimanî û kelîmeya wî binivîse",
visible: function (values) {
return !!values.pos;
},
disabledKeys: function (values) {
return {"navz": true, [values.lang]: true}
},
// Language dropdown + growing word-input list; a new row appends
// when the dropdown gets a value. Mirrors steps.trans, but flat.
buildRow: function (stepDiv) {
var row = $("<li class='wiktku-dunde-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.dunde.keys || [], steps.dunde.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.dunde.onLangChange(stepDiv, row);
});
// Word list for this language.
var wordList = $("<ul class='wiktku-dunde-words wiktku-lang-row-words'></ul>");
var firstWord = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/build below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-dunde-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Kelîme…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-dunde-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.dunde.makeWordInput();
list.append($("<li></li>").append(next));
}
});
return input;
},
onLangChange: function (stepDiv, row) {
var list = stepDiv.find("ul.wiktku-dunde-list");
var isLast = row.is(list.children("li.wiktku-dunde-row:last-child"));
if (isLast) {
var newRow = steps.dunde.buildRow(stepDiv);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across all lang selectors.
steps.dunde.syncDisabledKeys(stepDiv);
},
/** Disables already-selected languages in all other dropdowns. */
syncDisabledKeys: function (stepDiv) {
var taken = $.extend({}, steps.dunde.disabledKeys);
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
stepDiv.find("li.wiktku-dunde-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
build: function (stepDiv) {
if (stepDiv.find("ul.wiktku-dunde-list").length) return null;
var list = $("<ul class='wiktku-dunde-list wiktku-lang-row-list'></ul>");
list.append(steps.dunde.buildRow(stepDiv));
stepDiv.append(list);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return null;
},
/** Reads all [langcode, words[]] rows from the DOM. */
getRows: function (stepDiv) {
var result = [];
stepDiv.find("li.wiktku-dunde-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-dunde-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
output: function () {
var stepDiv = $("#wiktku-start-dunde");
var rows = steps.dunde.getRows(stepDiv);
if (!rows.length) return "";
var lines = $.map(rows, function (row) {
var lang = row[0], words = row[1];
return "* {{dû|" + lang + "|" + words.join("|") + "}}";
});
return "\n==== Dûnde ====\n" + lines.join("\n") + "\n";
}
},
zarava: {
id: "zarava",
name: "bi zaravayên din",
question_fmt: "'"+ pageName + "' di zaravayên din de çawa tê gotin?",
visible: function (values) {
return steps.trans.visible(values);
},
disabledKeys: {},
buildRow: function (group) {
var row = $("<li class='wiktku-zarava-row wiktku-lang-row'></li>");
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zaravayekî bigere");
populateMenus(langSel, steps.zarava.keys || [], steps.zarava.disabledKeys);
langSel.on("change", function () {
steps.zarava.onLangChange(group, row);
refresh();
});
var wordList = $("<ul class='wiktku-zarava-words wiktku-lang-row-words'></ul>");
var firstWord = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-zarava-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-zarava-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.zarava.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-zarava-list");
var isLast = row.is(list.children("li.wiktku-zarava-row:last-child"));
if (isLast) {
var newRow = steps.zarava.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
steps.zarava.syncDisabledKeys(group);
},
/** Disables already-selected dialects in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.zarava.disabledKeys);
group.find("li.wiktku-zarava-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-zarava-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
/** Builds (or fetches) the .wiktku-zarava-group for one gloss. */
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-zarava-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-zarava-list wiktku-lang-row-list'></ul>");
list.append(steps.zarava.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
/** Syncs one .wiktku-zarava-group per current gloss (same logic as steps.trans.build). */
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-zarava-group").remove();
return null;
}
var existingGroups = stepDiv.find(".wiktku-zarava-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.zarava.buildGroup(stepDiv, gi);
var label = group.find(".wiktku-zarava-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-zarava-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the dialect rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-zarava-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-zarava-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
/** Reads all rows from the DOM, grouped by gloss position. */
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-zarava-group").each(function () {
result.push(steps.zarava.getGroupTranslations($(this)));
});
return result;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-zarava");
var glosses = steps.trans.glossList();
if (!glosses.length) return "";
// A completely empty entry still gets a placeholder section
// to fill in later; an individual placeholder gloss is only
// skipped once some other sense already has real content.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.zarava.getGroupTranslations(group) : [];
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Bi zaravayên din ====\n" + blocks.join("\n") + "\n";
}
},
trans: {
id: "trans",
name: "werger",
question_fmt: "Tercimeyên vê maneyê bi zimanên din binivîse",
visible: function (values) {
if (!(values.lang === "ku" && values.pos)) return false;
// Hide only when every sense is a filled template sense (no plain
// gloss to attach translations to). A mix, or nothing filled in
// yet, still shows Werger.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRealSense = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
var hasFilledTplSense = dfnSenses.some(function (s) {
return s.tpl && s.tplTitle;
});
return hasRealSense || !hasFilledTplSense;
},
disabledKeys: {
"navz": true,
"ku": true,
"ckb": true,
"sdh": true,
"lki": true,
"hac": true,
"zza": true },
// Language dropdown + growing word-input list, scoped to one gloss's
// group (each gloss has its own group and row list).
buildRow: function (group) {
var row = $("<li class='wiktku-trans-row wiktku-lang-row'></li>");
// Language selector — the searchable picker is applied after insertion into DOM.
var langSel = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
langSel.attr("data-placeholder", "Li zimanekî bigere");
populateMenus(langSel, steps.trans.keys || [], steps.trans.disabledKeys);
// When a language is chosen, ensure a next empty row exists.
langSel.on("change", function () {
steps.trans.onLangChange(group, row);
refresh();
});
// Word list for this language.
var wordList = $("<ul class='wiktku-trans-words wiktku-lang-row-words'></ul>");
var firstWord = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(firstWord));
// The searchable picker is attached by initSelects() once the row
// is in the DOM (see onLangChange/buildGroup below).
row.append(langSel).append(wordList);
return row;
},
makeWordInput: function () {
var input = $("<textarea></textarea>")
.attr("name", "wiktku-trans-word-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.attr("placeholder", "Tercime…");
input.on("input", function () {
var list = $(this).closest("ul.wiktku-trans-words");
var item = $(this).parent();
var val = $.trim($(this).val());
if (val && item.is(list.children("li:last-child"))) {
var next = steps.trans.makeWordInput();
list.append($("<li></li>").append(next));
}
});
// Blur, not input -- refresh() re-syncs the whole form, same
// reasoning as the Mane textareas' blur-only refresh below.
input.on("blur", function () { refresh(); });
return input;
},
onLangChange: function (group, row) {
var list = group.find("ul.wiktku-trans-list");
var isLast = row.is(list.children("li.wiktku-trans-row:last-child"));
if (isLast) {
var newRow = steps.trans.buildRow(group);
list.append(newRow);
// Attach the searchable picker now the row is in the DOM.
initSelects(newRow);
}
// Keep disabled keys in sync across this gloss's lang selectors
// only — the same language can be used for different senses.
steps.trans.syncDisabledKeys(group);
},
/** Disables already-selected languages in all other dropdowns within one group. */
syncDisabledKeys: function (group) {
var taken = $.extend({}, steps.trans.disabledKeys);
group.find("li.wiktku-trans-row select").each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
group.find("li.wiktku-trans-row select").each(function () {
var self = $(this);
var own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (!code) return;
$(this).prop("disabled", taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
},
// Builds (or fetches) the .wiktku-trans-group for one gloss.
buildGroup: function (stepDiv, gi) {
var group = $("<div class='wiktku-trans-group'></div>").attr("data-gloss-pos", gi);
var list = $("<ul class='wiktku-trans-list wiktku-lang-row-list'></ul>");
list.append(steps.trans.buildRow(group));
group.append(list);
stepDiv.append(group);
// Attach the searchable picker now the select is in the DOM.
initSelects(list);
return group;
},
// Syncs one .wiktku-trans-group per current gloss, keyed by
// data-gloss-pos so groups stay matched across rebuilds.
build: function (stepDiv) {
var glosses = steps.trans.glossList();
if (!glosses.length) {
stepDiv.find(".wiktku-trans-group").remove();
return null;
}
// Drop groups for glosses that no longer exist (always from the
// tail — groups are only ever appended in gloss order below).
var existingGroups = stepDiv.find(".wiktku-trans-group");
if (existingGroups.length > glosses.length) {
existingGroups.slice(glosses.length).remove();
}
$.each(glosses, function (gi, gloss) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) group = steps.trans.buildGroup(stepDiv, gi);
// Only label groups when there's more than one gloss to
// distinguish between — a single gloss needs no caption.
var label = group.find(".wiktku-trans-group-label");
if (glosses.length > 1) {
if (!label.length) {
label = $("<div class='wiktku-trans-group-label'></div>");
group.prepend(label);
}
label.text(gloss.index + " " + gloss.text);
} else if (label.length) {
label.remove();
}
});
return null;
},
/** Reads the translation rows for a single gloss group. */
getGroupTranslations: function (group) {
var result = [];
group.find("li.wiktku-trans-row").each(function () {
var lang = $(this).find("select").val();
if (!lang) return;
var words = [];
$(this).find("ul.wiktku-trans-words textarea").each(function () {
var v = $.trim($(this).val());
if (v) words.push(v);
});
if (words.length) result.push([lang, words]);
});
return result;
},
// Reads all translation rows, grouped by gloss position.
getTranslations: function (stepDiv) {
var result = [];
stepDiv.find(".wiktku-trans-group").each(function () {
result.push(steps.trans.getGroupTranslations($(this)));
});
return result;
},
// Builds the numbered "{ index, gloss }" list, one per dfn sense
// with a real mane (skips template senses). index is 1-based and
// matches the sense's position in the "#" definition list.
// When nothing's filled in, returns one placeholder entry so
// Werger still has a group to attach to; when every sense is a
// filled template sense, stays empty (matches trans.visible()).
glossList: function () {
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var glosses = [];
var hasFilledTplSense = false;
$.each(dfnSenses, function (i, s) {
if (s.tpl) {
if (s.tplTitle) hasFilledTplSense = true;
return; // template sense — no gloss to caption with
}
var dfn = s.mane;
if (!dfn) {
// No mane, but other fields are filled — still gets a
// gloss group since dfn.getOutput() emits {{mane?}} for it.
if (senseHasExtraContent(s)) glosses.push({ index: i + 1, text: "" });
return;
}
if (dfn[0] !== dfn[0].toLowerCase()) dfn = decapitalize(dfn);
dfn = stripWikilinks(dfn).replace(/[.…?!)\]،؟]+$/, "");
glosses.push({ index: i + 1, text: dfn });
});
if (!glosses.length && !hasFilledTplSense) {
glosses.push({ index: 1, text: "" });
}
return glosses;
},
output: function (values) {
if (values.lang !== "ku") return "";
var stepDiv = $("#wiktku-start-trans");
var glosses = steps.trans.glossList();
// Every sense is a template sense — there's no real gloss
// anywhere and no group in the DOM either (glossList() only
// returns empty in that case), so skip the section entirely.
if (!glosses.length) return "";
// A completely empty entry (nothing filled in dfn at all, no
// şablon chosen either) still gets a placeholder Werger section
// to fill in later. Only
// skip an individual placeholder gloss below when some other
// sense actually has real content, making that particular
// placeholder redundant.
var dfnSenses = steps.dfn.getSenses($("#wiktku-start-dfn"));
var hasRegularMane = dfnSenses.some(function (s) {
return !s.tpl && (s.mane !== "" || senseHasExtraContent(s));
});
// Each gloss now has its own group of translation rows —
// attach every gloss's own words under its own werger-ser/-bin
// pair instead of dumping everything under the first one.
var blocks = $.map(glosses, function (gloss, gi) {
var group = stepDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
var rows = group.length ? steps.trans.getGroupTranslations(group) : [];
// Placeholder gloss (no real mane entered yet) with nothing
// translated into it. If some other sense already has real
// content, this placeholder is redundant — skip it. If the
// whole entry is empty, keep it so Werger still gets a
// section to fill in.
if (!gloss.text && !rows.length && hasRegularMane) return undefined;
if (rows.length) {
// Sort by Kurdish sort key.
rows.sort(function (a, b) {
var aLang = langsByCode[a[0]] || {};
var bLang = langsByCode[b[0]] || {};
return (aLang.sortKey || "").localeCompare(bLang.sortKey || "", "ku");
});
}
var wordLines = $.map(rows, function (row) {
var lang = row[0];
var words = row[1];
var wikiWords = words.map(function (w) {
return "{{W|" + lang + "|" + w + "}}";
}).join(", ");
return "* {{Z|" + lang + "}}: " + wikiWords;
}).join("\n");
var block = "{{werger-ser|" + (gloss.text || "?") + "|" + gloss.index + "}}";
if (wordLines) block += "\n" + wordLines;
block += "\n{{werger-bin}}";
return block;
});
if (!blocks.length) return "";
return "\n==== Werger ====\n" + blocks.join("\n") + "\n";
}
},
cavkani: {
id: "cavkani",
name: "çavkanî",
question_fmt: "Çavkaniyên din (ne jêgirtin) lê zêde bike",
visible: function (values) {
return !!values.pos;
},
// Fills the insert-dropdown from the members of [[Kategorî:Çavkanî]].
refreshTplSelect: function () {
var sel = $("#wiktku-start-cavkani .wiktku-tpl-insert-select");
if (!sel.length) return;
sel.find("option[value!='']").remove();
$.each(catSrcTemplates || [], function (_, name) {
sel.append($("<option></option>").val(name).text(name));
});
markTemplateOptions(sel, function (opt) { return opt.val(); });
// Tell the picker the option list changed.
sel.trigger("wiktku:refresh");
},
build: function (stepDiv) {
if (stepDiv.find(".wiktku-cavkani-wrap").length) return null;
var wrap = $("<div class='wiktku-cavkani-wrap'></div>");
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-cavkani-" + $.now())
.attr("class", "wiktku-start-step-field wiktku-cavkani-text")
.attr("rows", 4)
.attr("placeholder", "Şablonekî ji lîsteyê hilbijêre an rasterast binivîse…");
// Template insert dropdown, populated from [[Kategorî:Çavkanî]] —
// options refreshed once the category members are fetched.
var sel = $("<select class='wiktku-tpl-insert-select'></select>");
sel.attr("data-placeholder", "Şablonek lê zêde bike…");
sel.append($("<option value=''>— şablonek lê zêde bike —</option>"));
var status = $("<span class='wiktku-cavkani-status'></span>");
sel.on("change", function () {
var name = $(this).val();
if (!name) return;
insertAtCursor(ta[0], "* {{" + name + "}}\n");
$(this).val("");
sel.trigger("wiktku:refresh");
ta.focus();
});
wrap.append(sel);
wrap.append(status).append(ta);
stepDiv.append(wrap);
// build() runs again for every new beş (see the "+ Beşeke din"
// handler), after the initial page-wide pass — so this select needs
// its own init or it stays a plain, unsearchable native <select>.
initSelects(wrap);
if (catSrcTemplates) {
steps.cavkani.refreshTplSelect();
} else {
status.text("Şablon tên barkirin…");
fetchCatSrcTemplates().then(function () {
steps.cavkani.refreshTplSelect();
status.text("");
}, function () {
status.text("Şablon nehatin barkirin.");
});
}
return ta;
},
output: function () {
var text = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
if (!text) return "";
return "\n==== Çavkanî ====\n" + text + "\n";
}
},
};
steps.trans.keys = steps.lang.choices;
steps.dunde.keys = steps.lang.choices;
steps.zarava.keys = getZaravaKeys(steps.lang.choices);
// This array only controls DOM build order — nothing else keys off it
// (see the comment above stepIdToGroup). Mane tab order: Mane (senses,
// with malûmatên pêşketî nested inside each, see steps.dfn.build) →
// Wêne → Bi zaravayên din → Werger, so dfn/resim/zarava/trans sit
// adjacent to each other in that order.
var ordered_steps = [
steps.lang, steps.pos, steps.posparams,
steps.cinsiyet, steps.jimar, steps.lektur, steps.tr,
// Mane tab, in reading order: the senses, then the two things that
// belong to the entry as a whole rather than to one sense.
steps.dfn, steps.tewandin, steps.alfabeyendin, steps.zarava, steps.trans,
// Etîmolojî tab.
steps.etym, steps.dunde,
// Eleqedar tab — three lists of the same shape.
steps.je, steps.tekildar, steps.binereherwiha,
// Bilêvkirin isn't part of any STEP_GROUPS tab, but still needs a
// slot here because this array is what builds the DOM.
steps.pron,
// Beşên din fields — behind the picker inside the Eleqedar tab.
steps.resim,
steps.bnrh, steps.bikaranin, steps.formalt, steps.hevmane, steps.dijmane, steps.jornav, steps.jernav,
steps.cavkani, steps.xwendin];
// ── Step-group tabs ──────────────────────────────────────────────────────
// Three tabs: "Mane" holds the senses (whose own blocks carry Nimûne,
// Jêgirtin, Hevmane and Dijmane — see steps.dfn.allGroups), Tewandin,
// alfabeyendin, zarava and trans; "Etîmolojî" splits out that topical
// cluster; "Eleqedar" holds je/tekildar/binereherwiha plus — behind the
// "Beşên din" picker, which lives inside this tab rather than as its
// own — everything else (resim, formalt, hevmane, dijmane, jornav,
// jernav, bikaranin, cavkani, xwendin, bnrh). A newly-picked section is
// inserted right after the picker (see renderBesenDinPicker()), which
// sits above je/tekildar/binereherwiha in the DOM — so picking one
// pushes those down rather than the other way around. "mane" stays
// first as the default-active tab since Mane is required content.
// Tewandin keeps its own free-text {{ku-tewîn-nav}} field and does NOT
// absorb the Cins/zayend form parameters (m=/n=/p=), which stay on the
// headword template.
// Steps that live behind the "Beşên din" picker inside Eleqedar — only
// these need an explicit "open" before they're shown, even once
// Eleqedar's tab is active.
var BESENDIN_STEP_IDS = ["resim", "formalt", "hevmane", "dijmane", "jornav", "jernav", "bikaranin", "cavkani", "xwendin", "bnrh"];
var STEP_GROUPS = [
{ id: "mane", label: "Mane", steps: ["dfn", "tewandin", "alfabeyendin", "zarava", "trans"] },
{ id: "etimoloji", label: "Etîmolojî", steps: ["etym", "dunde"] },
{ id: "eleqedar", label: "Eleqedar", steps: ["je", "tekildar", "binereherwiha"].concat(BESENDIN_STEP_IDS) },
];
// Reverse lookup: step id -> index into STEP_GROUPS (undefined for
// ungrouped steps — now only lang/pos/posparams/cinsiyet/lektur/tr,
// which stay always-visible since they're required to get to everything
// else).
var stepIdToGroup = {};
$.each(STEP_GROUPS, function (gi, g) {
$.each(g.steps, function (_, sid) { stepIdToGroup[sid] = gi; });
});
// Step ids that belong to each section (not shared across sections).
var PER_SECTION_STEP_IDS = [
"pos", "posparams", "cinsiyet", "jimar", "lektur", "tr",
"resim", "dfn", "tewandin", "formalt", "bikaranin", "alfabeyendin", "hevmane", "dijmane", "jornav", "jernav", "je", "tekildar", "etym", "dunde", "binereherwiha", "zarava", "trans", "cavkani", "xwendin"
];
// ── Multi-section state ──────────────────────────────────────────────────
// Each entry is a plain snapshot: { pos, cinsiyet, lektur, tr, dfn (senses[]),
// je, tekildar, binereherwiha, trans (rows[]) }.
var sections = [{}]; // section 0 starts empty (DOM is the truth)
var activeSection = 0;
// ── Bilêvkirin as a standalone section ───────────────────────────────
// Pronunciation belongs to the word, not to one part of speech, so it
// sits beside Navdêr/Lêker in the tab bar rather than inside any of them.
// Modelled as a flag rather than an entry in `sections` since
// activeSection is used as an array index in many places — viewing
// Bilêvkirin leaves activeSection pointing at the POS section you were on.
var viewingPron = false; // is it the tab currently on screen
// Which STEP_GROUPS tab is currently shown. Lives out here (like
// activeSection above) so refresh() — which is not nested inside build()
// — can read it and so renderGroupTabs()/updateGroupTabIndicators() below
// can be called from refresh() too. Both operate on the tab bar via its
// #wiktku-group-tabs id rather than a closured element reference, for the
// same reason.
var activeGroup = 0;
// Unsaved-changes / autosave state. Lives out here (same reason as
// activeGroup above) so destroy() — which runs on final submit and on
// switching to the source editor, and is not nested inside build() —
// can clear the timers and the beforeunload warning. build() assigns
// into these; it doesn't re-declare them.
var formDirty = false;
var autosaveDebounceTimer = null;
var autosaveInterval = null;
/** Re-renders the step-group tab bar to reflect the current activeGroup. */
// Scrolls so the given tab bar (by element id) sits at the top of the
// viewport, below the sticky site header/section-tabs offset. Switching
// tabs can leave the viewport wherever it happened to be scrolled in the
// previous tab's (possibly much longer or shorter) content, otherwise —
// the new tab's content might open off-screen entirely.
//
// Switching tabs can also shorten the page (e.g. leaving a long "Beşên
// din" field), and the browser may clamp the scroll position on its own
// timing before our measurement runs. Deferring one frame lets that
// settle first, so the offsetTop read below is the final, post-refresh
// one.
function scrollTabBarIntoView(barId) {
window.requestAnimationFrame(function () {
var bar = document.getElementById(barId);
if (!bar) return;
var form = document.getElementById("wiktku-start-form");
var stabsH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-stabs-h")) || 0) : 0;
var siteHeaderH = form ? (parseFloat(form.style.getPropertyValue("--wiktku-siteheader-h")) || 0) : 0;
var top = bar.getBoundingClientRect().top + window.pageYOffset - stabsH - siteHeaderH;
$("html, body").animate({ scrollTop: Math.max(0, top) }, 200);
});
}
function renderGroupTabs() {
var groupTabsDiv = $("#wiktku-group-tabs");
if (!groupTabsDiv.length) return;
groupTabsDiv.empty();
$.each(STEP_GROUPS, function (i, g) {
var tab = $("<span></span>")
.addClass("wiktku-group-tab")
.attr("data-group-idx", i)
.text(g.label)
.toggleClass("wiktku-tab-active", i === activeGroup);
(function (idx) {
tab.on("click", function () {
if (idx === activeGroup) return;
activeGroup = idx;
renderGroupTabs();
if (idx === ELEQEDAR_GROUP_IDX) renderBesenDinPicker();
refresh();
scrollTabBarIntoView("wiktku-group-tabs");
});
}(i));
groupTabsDiv.append(tab);
});
}
/**
* Toggles a "needs attention" dot on any group tab that still has a
* required, unfilled field hidden inside it — so switching tabs doesn't
* leave the person wondering why "Bişîne" stays disabled.
*/
function updateGroupTabIndicators(values) {
var tabs = $("#wiktku-group-tabs .wiktku-group-tab");
if (!tabs.length) return;
$.each(STEP_GROUPS, function (i, g) {
var missing = false;
$.each(g.steps, function (_, sid) {
var step = steps[sid];
if (!step || !step.required) return;
var val = values[sid];
if (val === "" || (step.multiple && $.isArray(val) && val.join("") === "")) {
missing = true;
}
});
tabs.eq(i).toggleClass("wiktku-tab-missing", missing && i !== activeGroup);
});
}
// "Beşên din" reveals one field at a time via a picker rather than
// stacking them all. Per-section state, snapshotted in
// saveCurrentSection() and restored in restoreSection() below, and
// auto-seeded from any field that already has content (so a returning
// draft doesn't hide something already filled in).
var openedOtherSections = {};
var BESENDIN_SET = {};
$.each(BESENDIN_STEP_IDS, function (_, sid) { BESENDIN_SET[sid] = true; });
// "eleqedar"'s index in STEP_GROUPS — the Beşên din picker now renders
// inside this tab rather than having its own. Derived rather than
// hardcoded — this was once a literal 2 and silently pointed at the
// wrong tab the moment Etîmolojî and Eleqedar were reordered.
var ELEQEDAR_GROUP_IDX = (function () {
for (var i = 0; i < STEP_GROUPS.length; i++) {
if (STEP_GROUPS[i].id === "eleqedar") return i;
}
return STEP_GROUPS.length - 1;
}());
/** Re-renders the "Beşên din" picker to list only the not-yet-opened sections. */
function renderBesenDinPicker() {
var wrap = $("#wiktku-besendin-picker");
if (!wrap.length) return;
wrap.empty();
var pickerValues = getValues();
var unopened = $.grep(BESENDIN_STEP_IDS, function (sid) {
if (openedOtherSections[sid]) return false;
// Don't offer a section that would open onto nothing: several of
// these are gated (Bi zaravayên din / Werger need lang=ku, most
// need a pos). refresh() would hide it again straight away, so
// picking it would read as a broken dropdown.
var step = steps[sid];
if (step && typeof step.visible === "function") {
try {
if (!step.visible(pickerValues)) return false;
} catch (e) { /* a step whose visible() needs DOM we lack yet — offer it */ }
}
return true;
});
if (!unopened.length) return; // everything's open — nothing left to pick
var sel = $("<select class='wiktku-besendin-select'></select>");
sel.attr("data-placeholder", "— binebeşên din hilbijêre —");
sel.append($("<option value=''>— binebeşên din hilbijêre —</option>"));
$.each(unopened, function (_, sid) {
var step = steps[sid];
if (!step) return;
sel.append($("<option></option>").val(sid).text(capitalize(step.name)));
});
sel.on("change", function () {
var sid = $(this).val();
if (!sid) return;
openedOtherSections[sid] = true;
// Newly opened section moves right under the picker, ahead of
// whatever's already open, instead of wherever it happened to
// sit in the fixed step order.
$("#wiktku-start-" + sid).insertAfter(wrap);
renderBesenDinPicker();
refresh();
});
wrap.append(sel);
initSelects(wrap);
}
/** Marks any BESENDIN_STEP_IDS field that already has real content as
* "opened", so restoring a draft doesn't bury filled-in sections behind
* the picker. Reuses each step's own output() as the emptiness check —
* it already knows what "empty" means per field, and it reads the DOM
* the restore just populated. Call this AFTER the DOM has the restored
* values in it. */
function seedOpenedFromContent() {
var values = getValues();
$.each(BESENDIN_STEP_IDS, function (_, sid) {
if (openedOtherSections[sid]) return;
var step = steps[sid];
if (!step || typeof step.output !== "function") return;
try {
if (step.output(values)) openedOtherSections[sid] = true;
} catch (e) { /* a step whose output() needs more state than we have yet — skip it */ }
});
}
function snapshotDfn() {
return steps.dfn.getSenses($("#wiktku-start-dfn"));
}
// Rebuilds the dfn DOM from a previously snapshotted senses array.
function restoreDfn(senses) {
var stepDiv = $("#wiktku-start-dfn");
// Tear down the existing tab bar + list so build() recreates them cleanly.
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
var list = stepDiv.find("ol.wiktku-dfn-list");
$.each(senses, function (i, sense) {
// Ensure enough blocks exist (build() seeds one).
var blocks = list.find("li.wiktku-dfn-sense");
var block = blocks.eq(i);
if (!block.length) {
var nb = steps.dfn.buildSenseBlock(stepDiv);
list.append(nb);
block = list.find("li.wiktku-dfn-sense").last();
}
// Template selector.
block.find("select.wiktku-dfn-tpl-select").val(sense.tpl || "");
if (sense.tpl) {
block.find("textarea.wiktku-dfn-mane").hide();
block.find("input.wiktku-dfn-tpl-title").val(sense.tplTitle || "").show();
// The picker is collapsed behind a link by default; a restored
// sense that already uses a template has to show it, or the
// chosen template is invisible and looks lost.
var reveal = block.data("revealTplRow");
if (typeof reveal === "function") reveal();
} else {
block.find("textarea.wiktku-dfn-mane").val(sense.mane || "").show();
block.find("input.wiktku-dfn-tpl-title").hide().val("");
}
// dfnArea is the only thing actually saved, so split it back into
// tercime/îzah for editing (mirrors syncNonKuToMane's join format).
var maneParts = splitTercimeIzah(sense.mane);
block.find(".wiktku-dfn-tercime").val(maneParts ? maneParts.tercime : (sense.mane || ""));
block.find(".wiktku-dfn-izah").val(maneParts ? maneParts.izah : "");
// Re-sync the formên alternatîv/hevmane/… hide state now that
// tplSelect's value was set (its own "change"
// handler doesn't fire from .val(), so this doesn't happen otherwise).
var updateVis = block.data("updateFieldVisibility");
if (updateVis) updateVis();
// Same for the mane-box mode (plain vs tercime+îzah), since this
// also depends on tplSelect's (and lang's) value.
var updateMane = block.data("updateManeMode");
if (updateMane) updateMane();
// Restore a sub-list (etiket, formên alternatîv, hevmane …) from a values array.
function restoreSubList(cls, values) {
if (!values || !values.length) return;
var groupDiv = block.find("." + cls + "-group");
var subList = groupDiv.find("ol, ul").first();
if (!subList.length) return; // group not found
var useModifiers = !!MODIFIER_SUBGROUPS[cls];
// Fill existing items, append new ones as needed.
$.each(values, function (j, val) {
var items = subList.find("li");
if (j < items.length) {
// useModifiers rows keep the word and its modifier tags in
// separate elements (see makeModifierLi) — setRawValue()
// re-splits val() the same way parsing it fresh would,
// instead of dumping the raw "word<key:value>" string into
// the visible word box.
var setRaw = items.eq(j).data("setRawValue");
if (useModifiers && typeof setRaw === "function") {
setRaw(val);
} else {
items.eq(j).find("textarea").val(val);
}
} else {
var ta = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow")
.val(val);
ta.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(useModifiers ? makeModifierLi(ta) : $("<li></li>").append(ta));
}
});
if (!useModifiers) return;
// A fresh empty row goes on the end so there's always somewhere
// to add the next word without first having to click into a
// filled one.
var filled = subList.find("li.wiktku-word-li");
if (filled.last().is(":last-child")) {
var nextTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-restore-next-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 1)
.attr("class", "wiktku-dfn-subfield wiktku-start-narrow");
nextTa.on("input", function () {
steps.dfn.onSubInput(subList, this, 1, true, useModifiers);
});
subList.append(makeModifierLi(nextTa));
}
}
// Reveal and populate lazy groups that have data.
var lazyMap = {
"wiktku-dfn-alternativ": sense.alternativ,
"wiktku-dfn-hevmane": sense.hevmane,
"wiktku-dfn-dijmane": sense.dijmane,
"wiktku-dfn-koordine": sense.koordine,
"wiktku-dfn-jornav": sense.jornav,
"wiktku-dfn-jernav": sense.jernav
};
$.each(lazyMap, function (cls, vals) {
if (vals && vals.length) restoreSubList(cls, vals);
});
// Dever/Şîve, Etîket, Kategorî — chip fields, not textarea
// sub-lists, restored via the adders buildSenseBlock stashed on
// the block.
var chipRestoreMap = {
addDeverChip: sense.dever,
addEtiketChip: sense.etiket,
addKategoriChip: sense.kategori
};
$.each(chipRestoreMap, function (dataKey, vals) {
var addChip = block.data(dataKey);
if (addChip && vals && vals.length) {
$.each(vals, function (_, v) { addChip(v); });
}
});
// Nimûne items are objects { text, tr?, t? } — restore using the builder.
if (sense.nimune && sense.nimune.length) {
var nimuneGroupDiv = block.find(".wiktku-dfn-nimune-group");
var nimuneList = nimuneGroupDiv.find("ol.wiktku-dfn-nimune-list");
var buildFn = nimuneGroupDiv.data("buildNimuneLi");
nimuneList.empty();
$.each(sense.nimune, function (_, n) {
var text = typeof n === "object" ? (n.text || "") : n;
if (!text) return;
var li = buildFn ? buildFn() : $("<li class='wiktku-dfn-nimune-li'></li>");
li.find(".wiktku-dfn-nimune-text").val(text).each(function () { highlightLinebreak(this); });
if (n.tr) li.find(".wiktku-dfn-nimune-tr").val(n.tr);
if (n.t) li.find(".wiktku-dfn-nimune-t").val(n.t);
nimuneList.append(li);
});
// Always leave a blank trailing item.
if (buildFn) nimuneList.append(buildFn());
}
// Restore jêder entries — accepts current array shape or a legacy
// single object (older saved sections) for backwards compat.
var jederArr = $.isArray(sense.jeder) ? sense.jeder : (sense.jeder ? [sense.jeder] : []);
if (jederArr.length) {
var jederGroupDiv = block.find(".wiktku-dfn-jeder-group");
var jederList = jederGroupDiv.find("ol.wiktku-dfn-jeder-list");
var buildJederFn = jederGroupDiv.data("buildJederLi");
jederList.empty();
$.each(jederArr, function (_, j) {
if (!j || !j.quote) return;
var li = buildJederFn ? buildJederFn() : $("<li class='wiktku-dfn-jeder-li'></li>");
li.find(".wiktku-dfn-nimune-quote").val(j.quote).each(function () { highlightLinebreak(this); });
if (j.quoteTr) li.find(".wiktku-dfn-nimune-quote-tr").val(j.quoteTr);
if (j.quoteT) li.find(".wiktku-dfn-nimune-quote-t").val(j.quoteT);
if (j.jederManual) {
var manualSel = li.find(".wiktku-dfn-quote-src-sel");
manualSel.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val(j.jederManual);
} else if (j.jederCatTpl) {
// Legacy format from before the şablon search switched rows to
// manual mode directly — convert it the same way now, so it's
// editable like any other free-form source.
var catSelEl = li.find(".wiktku-dfn-quote-src-sel");
catSelEl.val("__manual__").trigger("change");
li.find(".wiktku-dfn-quote-src-manual").val("{{" + j.jederCatTpl + "}}");
} else if (j.jederTpl) {
var srcSel = li.find(".wiktku-dfn-quote-src-sel");
srcSel.val(j.jederTpl).trigger("change");
if (j.jêderFields) {
$.each(j.jêderFields, function (key, val) {
li.find(".wiktku-dfn-quote-src-input[data-field-key='" + key + "']").val(val);
});
}
}
jederList.append(li);
});
// Always leave a blank trailing item, same as Nimûne.
if (buildJederFn) jederList.append(buildJederFn());
}
});
// Unlike addSense(), build()/buildSenseBlock() above don't wrap their own <select>s -- do it once for the whole restored list (idempotent, so already-wrapped selects are skipped).
initSelects(list);
// Restoring a draft opens the first sense's tab by default.
steps.dfn.setOpenSense(stepDiv, list.find("li.wiktku-dfn-sense").first());
}
// Snapshots simple (non-dfn) per-section steps from the DOM into obj.
function saveCurrentSection() {
var snap = {};
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
snap.dfn = snapshotDfn();
return;
}
if (id === "etym") {
snap.etym = $.trim($("#wiktku-start-etym .wiktku-etym-text").val());
return;
}
if (id === "cavkani") {
snap.cavkani = $.trim($("#wiktku-start-cavkani .wiktku-cavkani-text").val());
return;
}
if (id === "posparams") {
snap.posparams = $.trim($("#wiktku-start-posparams .wiktku-posparams-text").val());
return;
}
if (id === "resim") {
// One entry per row, including blank ones, so a mid-edit draft
// (e.g. a filename typed but no caption yet) survives a cache
// restore instead of being silently dropped.
snap.resim = $("#wiktku-start-resim li.wiktku-resim-item").map(function () {
return {
file: $.trim($(this).find(".wiktku-resim-file").val()),
caption: $.trim($(this).find(".wiktku-resim-caption").val()),
alt: $.trim($(this).find(".wiktku-resim-alt").val())
};
}).get();
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
if (id === "trans") {
snap.trans = steps.trans.getTranslations(stepDiv);
return;
}
if (id === "zarava") {
snap.zarava = steps.zarava.getTranslations(stepDiv);
return;
}
if (id === "dunde") {
snap.dunde = steps.dunde.getRows(stepDiv);
return;
}
// Generic: read all step fields.
var fields = stepDiv.find(".wiktku-start-step-field");
if (step.multiple) {
snap[id] = fields.map(function () { return $(this).val(); }).get();
} else {
snap[id] = fields.val();
}
});
// Which "Beşên din" sub-sections this section has already opened
// via the picker — per-section, like everything else here.
snap.openedOtherSections = $.extend({}, openedOtherSections);
sections[activeSection] = snap;
}
// Restores a previously saved snapshot into the form DOM.
function restoreSection(snap) {
$.each(PER_SECTION_STEP_IDS, function (_, id) {
if (id === "dfn") {
restoreDfn(snap.dfn || []);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
$("#wiktku-start-etym .wiktku-etym-text").val(snap.etym || "");
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
$("#wiktku-start-cavkani .wiktku-cavkani-text").val(snap.cavkani || "");
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
steps.posparams.build(ppDiv);
var savedTa = snap.posparams || "";
ppDiv.find(".wiktku-posparams-text").val(savedTa);
// Re-parse saved textarea to restore helper widget states.
if (savedTa) {
var cMatch = savedTa.match(/\|c=([^|}\s]+)/);
var jMatch = savedTa.match(/\|j=([^|}\s]+)/);
var trMatch = savedTa.match(/\|tr=([^|}\s]+)/);
// Write into the shared hidden field (not the visible
// select, which has no <option>s yet) — syncHelpers()
// below reads it back to restore the dropdown once the
// gender/number options for this language are populated.
if (cMatch) $("#wiktku-start-cinsiyet-field").val(cMatch[1]);
if (jMatch) $("#wiktku-start-jimar-field").val(jMatch[1]);
if (trMatch) ppDiv.find(".wiktku-posparams-field[data-pp-key='tr']").val(trMatch[1]);
// For lêker, c= holds the verb-type code instead of a
// gender — split it back across the two dropdowns it is
// now assembled from ("x-gh" → form "x" + base "gh").
if (cMatch && /^lêker/.test(snap.pos || "")) {
var lekturParts = /^(?:([xphb])-)?(n?gh)$/.exec(cMatch[1]);
if (lekturParts) {
ppDiv.find(".wiktku-posparams-lektur-sel").val(lekturParts[2]);
ppDiv.find(".wiktku-posparams-lekturform-sel").val(lekturParts[1] || "");
}
}
// Restore checked optional params via the same param/value overrides rebuildTa uses.
ppDiv.find(".wiktku-posparams-opt-panel input[type='checkbox']").each(function () {
var key = $(this).val();
var override = steps.posparams.OPT_PARAM_OVERRIDES[key];
var needle = override ? "|" + override.param + "=" + override.value : "|" + key + "=1";
$(this).prop("checked", savedTa.indexOf(needle) !== -1);
});
}
steps.posparams.syncHelpers(ppDiv);
return;
}
if (id === "resim") {
var resimDiv = $("#wiktku-start-resim");
resimDiv.find(".wiktku-resim-wrap").remove();
steps.resim.build(resimDiv);
// Accept both the new array-of-rows format and the older
// single {file, caption} object saved before multi-image
// support existed.
var resimSnap = snap.resim;
var rows = $.isArray(resimSnap) ? resimSnap : (resimSnap ? [resimSnap] : []);
if (rows.length) {
var list = resimDiv.find("ol.wiktku-resim-list");
list.empty();
$.each(rows, function (_, r) {
var item = steps.resim.buildItem(resimDiv);
item.find(".wiktku-resim-file").val(r.file || "");
item.find(".wiktku-resim-caption").val(r.caption || "");
item.find(".wiktku-resim-alt").val(r.alt || "");
list.append(item);
});
// Always keep a blank trailing row, same as Nimûne/Dûnde.
var lastItem = list.children("li.wiktku-resim-item").last();
if ($.trim(lastItem.find(".wiktku-resim-file").val())) {
list.append(steps.resim.buildItem(resimDiv));
}
}
return;
}
if (id === "trans") {
// Rebuild trans groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
steps.trans.build(transDiv);
var groupsSnap = snap.trans || [];
$.each(groupsSnap, function (gi, rows) {
var group = transDiv.find(".wiktku-trans-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-trans-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-trans-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-trans-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.trans.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.trans.makeWordInput()));
}
});
});
return;
}
if (id === "zarava") {
// Rebuild zarava groups (dfn must already be restored by this
// point in the $.each loop, so glossList() reflects it).
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
steps.zarava.build(zaravaDiv);
var zaravaGroupsSnap = snap.zarava || [];
$.each(zaravaGroupsSnap, function (gi, rows) {
var group = zaravaDiv.find(".wiktku-zarava-group[data-gloss-pos='" + gi + "']");
if (!group.length) return; // gloss no longer exists — drop its rows
var list = group.find("ul.wiktku-zarava-list");
$.each(rows, function (j, row) {
var lang = row[0], words = row[1];
var lastRow = list.find("li.wiktku-zarava-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-zarava-words");
$.each(words, function (k, word) {
var wordItems = wordList.find("li");
if (k < wordItems.length) {
wordItems.eq(k).find("textarea").val(word);
} else {
var extraInput = steps.zarava.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.zarava.makeWordInput()));
}
});
});
return;
}
if (id === "dunde") {
// Rebuild dunde rows.
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
steps.dunde.build(dundeDiv);
var dundeRows = snap.dunde || [];
$.each(dundeRows, function (i, row) {
var lang = row[0], words = row[1];
var list = dundeDiv.find("ul.wiktku-dunde-list");
var lastRow = list.find("li.wiktku-dunde-row").last();
lastRow.find("select").val(lang).trigger("change");
var wordList = lastRow.find("ul.wiktku-dunde-words");
$.each(words, function (j, word) {
var wordItems = wordList.find("li");
if (j < wordItems.length) {
wordItems.eq(j).find("textarea").val(word);
} else {
// .val() below doesn't fire "input", so the normal
// auto-grow never runs — append a matching item
// ourselves instead of silently dropping the word.
var extraInput = steps.dunde.makeWordInput();
wordList.append($("<li></li>").append(extraInput));
extraInput.val(word);
}
});
// Leave one trailing empty field so the row can still grow
// when the user types, matching a freshly-built row.
if ($.trim(wordList.children("li").last().find("textarea").val())) {
wordList.append($("<li></li>").append(steps.dunde.makeWordInput()));
}
});
return;
}
var step = steps[id];
if (!step) return;
var stepDiv = $("#wiktku-start-" + id);
var fields = stepDiv.find(".wiktku-start-step-field");
var val = snap[id];
if (val === undefined || val === null) return;
if (step.multiple) {
var arr = val;
fields.each(function (i) {
if (i < arr.length) $(this).val(arr[i]);
});
} else {
fields.val(val);
}
// Tell the pickers about the programmatic value change.
fields.filter("select").trigger("wiktku:refresh");
});
// Restore which "Beşên din" sub-sections were already opened for
// this section, then seed in anything the DOM restore above just
// filled in but that wasn't marked opened yet.
openedOtherSections = $.extend({}, snap.openedOtherSections || {});
seedOpenedFromContent();
renderBesenDinPicker();
}
function getWikitextForCurrentSection(callback) {
var values = getValues();
var entry = steps.pos.output(values);
// Bilêvkirin is NOT emitted here — it is one shared field for the
// whole word, so emitting it per section duplicated it. getWikitext()
// prints it once, above the POS sections.
steps.dfn.getOutput(values, function (output) {
entry += output;
entry += steps.tewandin.output(values);
entry += steps.bikaranin.output(values);
entry += steps.alfabeyendin.output(values);
entry += steps.formalt.output(values);
entry += steps.hevmane.output(values);
entry += steps.dijmane.output(values);
entry += steps.jornav.output(values);
entry += steps.jernav.output(values);
entry += steps.je.output(values);
entry += steps.tekildar.output(values);
entry += steps.etym.output(values);
entry += steps.dunde.output(values);
entry += steps.binereherwiha.output(values);
entry += steps.zarava.output(values);
entry += steps.trans.output(values);
entry += steps.cavkani.output(values);
entry += steps.xwendin.output(values);
callback(entry, values);
});
}
function getWikitextAllSections(callback) {
saveCurrentSection();
var results = new Array(sections.length);
var sharedValues = getValues(); // lang/pron are shared
function processSection(i) {
// Each hop through here can be reached asynchronously: the dfn
// step waits on mw.loader.using("ext.gadget.linkify") before
// calling back, so from that point on the submit handler's own
// try/catch is no longer on the stack. Guard every hop instead,
// or a throw after linkify loads vanishes into an unhandled
// rejection and the form just sits there looking busy.
try {
if (i >= sections.length) {
// All done — restore original active section.
restoreSection(sections[activeSection]);
refresh();
callback(results, sharedValues);
return;
}
restoreSection(sections[i]);
refresh();
getWikitextForCurrentSection(function (sectionText) {
results[i] = sectionText;
processSection(i + 1);
});
} catch (e) {
if (typeof onSubmitError === "function") onSubmitError(e);
else throw e;
}
}
processSection(0);
}
// Assembles wikitext for all sections plus the shared lang header.
function getWikitext(callback) {
getWikitextAllSections(function (sectionTexts, sharedValues) {
var bnrh = steps.bnrh.output(sharedValues);
var entry = steps.lang.output(sharedValues);
// One pronunciation per word, printed once between the language
// header and the POS sections — see steps.pron.output.
entry += steps.pron.output(sharedValues);
entry += sectionTexts.join("\n");
// Reconstruction-namespace pages (ns 108) mark the language
// section with {{jinûvesazkirî|langcode}} directly above it —
// only prepended when generating fresh wikitext, same as h-bnr
// below (addLangMode handles an existing page separately).
if (!addLangMode && ns === 108) entry = "{{jinûvesazkirî|" + sharedValues.lang + "}}\n" + entry;
// h-bnr goes before the language section — only prepended when
// generating fresh wikitext (not addLangMode, which handles it separately).
if (!addLangMode && bnrh) entry = bnrh + entry;
// Edit summary uses section 0's values. Read from the saved snapshot
// (not the live DOM) so it doesn't mix section 0's pos with whichever
// section happens to be active/restored at this point.
var dfnSenses = (sections[0] && sections[0].dfn) || [];
var dfnSummary = dfnSenses.map(function (s) {
if (s.mane) return s.mane;
// Template sense (e.g. {{guharto|ku|somepage}}) — same
// construction as the tplCall built in getOutput, so the
// summary shows what actually gets written to the entry
// instead of going blank.
if (s.tpl && s.tplTitle) {
return s.tplNoLang
? "{{" + s.tpl + "|" + s.tplTitle + "}}"
: "{{" + s.tpl + "|" + sharedValues.lang + "|" + s.tplTitle + "}}";
}
return "";
}).filter(Boolean).join("; ");
var summary = "([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +" +
sharedValues.lang + ":" + sections[0].pos + ":" + dfnSummary;
var summaryField = document.getElementById("wpSummary");
if (addLangMode && summaryField.value) {
summary = summaryField.value + " " + summary;
}
summaryField.value = summary;
callback(entry);
});
}
var continueBtn;
// Set by build() to its submitFailed(). Lives out here because the
// wikitext-assembly functions below are outside build()'s closure but
// still need a way to report a failure to the person rather than dying
// silently in the console.
var onSubmitError = null;
// Whether Mane/Bi zaravayên din/Werger has anything in it — kept
// outside refresh() so the click handler can read the latest value.
var wiktkuContentGateOk = true;
function getValuesForStep(stepId) {
var fields = $("#wiktku-start-" + stepId + " .wiktku-start-step-field");
var step = steps[stepId];
if (step.keys) {
var values = {};
fields.each(function (i, field) {
var key = $(field).prevAll(".wiktku-start-step-keyfield").val();
if (key) values[key] = $(field).val();
});
return values;
}
else if (step.multiple) {
return fields.map(function () {
return $(this).val();
}).get();
}
return fields.val();
}
function getValues() {
var values = {};
$.each(steps, function (stepId) {
values[stepId] = getValuesForStep(stepId);
});
return values;
}
function populateMenus(inputs, choices, disabledValues, placeholderText) {
if (!disabledValues) disabledValues = {};
var vals = inputs.map(function (i, input) {
return $(input).val();
});
inputs.empty();
inputs.append($("<option></option>").text(placeholderText || ""));
$.each(choices, function (j, choice) {
// Append the language code in parentheses so that typing a code
// (e.g. "ku") in the search box still finds the language name.
var displayText = capitalize(choice.label);
if (choice.sortKey !== undefined) {
// This is a language choice {code, label, sortKey} — show code.
displayText = capitalize(choice.label) + " (" + choice.code + ")";
}
var option = $(mw.html.element("option", {
value: choice.code }, displayText));
if (disabledValues[choice.code]) option.prop("disabled", true);
inputs.append(option);
});
inputs.val(function (i) {
return vals[i];
});
}
// Keys to disable for a key menu: already-taken or explicitly disabled.
function getDisabledKeys(step) {
var occupiedKeys = {};
$("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield").each(function () {
occupiedKeys[$(this).val()] = true;
});
if (step.disabledKeys) $.extend(occupiedKeys, step.disabledKeys);
return occupiedKeys;
}
function buildKeyInput(step) {
if (!step.keys) return undefined;
var input = $(mw.html.element("select", {
"class": "wiktku-start-step-keyfield wiktku-start-narrow" }));
input.attr("data-placeholder", "Hilbijêre " + step.keyName);
populateMenus(input, step.keys, getDisabledKeys(step));
// Keep the key menus mutually exclusive: whatever one row selects is
// disabled in every other row. Recomputed from the DOM on each change.
input.on("change", function () {
var all = $("#wiktku-start-" + step.id + " .wiktku-start-step-keyfield");
var taken = {};
all.each(function () {
var v = $(this).val();
if (v) taken[v] = true;
});
all.not(this).each(function () {
var self = $(this), own = self.val();
self.find("option").each(function () {
var code = $(this).val();
if (code) $(this).prop("disabled", !!taken[code] && code !== own);
});
self.trigger("wiktku:refresh");
});
});
// Return the wrapper; the select lives inside it and still fires change.
return wrapNativeSelect(input);
}
// Adds another text field to the list if the last one now has text.
function refreshForTextField(currentInput, step) {
currentInput = $(currentInput);
var value = currentInput.val();
var item = currentInput.parent();
if (value && !item.next("li").length) {
var nextInput = $("<textarea></textarea>")
.attr("name", "wiktku-field-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("class", currentInput.attr("class"))
.attr("rows", currentInput.attr("rows") || 1);
if (step.placeholder) {
nextInput.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
nextInput.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var nextKeyInput = buildKeyInput(step);
var nextItem = step.modifiers
? makeModifierLi(nextInput).prepend(nextKeyInput || "")
: $("<li></li>").append(nextKeyInput).append(nextInput);
item.parent().append(nextItem);
if (nextKeyInput) initSelects(nextItem);
}
var canContinue = !continueBtn.isDisabled();
if ((value && !canContinue) || (!value && canContinue)) {
refresh(step);
}
}
function prepareInput(input, step) {
if (!input) return;
if (step.required) input.prop('required', true);
input.change(step, refresh);
}
// Shows/hides steps based on whether their prerequisites are met, and
// updates placeholders where applicable.
function refresh(evt) {
var values = getValues();
var changedStep = evt && evt.data;
var canContinue = true;
// Re-filter template options in every sense block whenever lang changes.
$("#wiktku-start-dfn .wiktku-dfn-tpl-select").each(function () {
var fn = $(this).data("updateOptions");
if (typeof fn === "function") fn();
});
// Re-filter the pronunciation template dropdown when language changes -- etym/cavkani's lists don't depend on lang, so they're only built once at data load instead of on every refresh().
steps.pron.refreshTplSelect();
// Re-render any step's question label that depends on current values
// (e.g. dfn's Werger-section reminder, which only applies to ku).
$.each(steps, function (stepId, step) {
if (typeof step.question_fmt === "function") {
$("#wiktku-start-" + stepId + " .wiktku-start-step-question")
.text(step.question_fmt(values));
}
});
// Pos's definition/example text, right under its dropdown — see
// steps.pos.example and the .wiktku-start-pos-example build above.
$("#wiktku-start-pos .wiktku-start-pos-example").text(steps.pos.example(values));
// Sync every sense block's mane-box mode (plain mane vs tercime+îzah)
// to the current language, in case lang changes after blocks exist.
// Same pass also re-syncs Dever/Şîve's default open/closed state
// (open for ku, closed otherwise — see updateDeverOpenState) and the
// "Malûmatên din" accordion's collapsed-state summary/count,
// in case any of its fields changed since the last sync.
$("#wiktku-start-dfn li.wiktku-dfn-sense").each(function () {
var updateMane = $(this).data("updateManeMode");
if (updateMane) updateMane();
var updateDeverOpen = $(this).data("updateDeverOpenState");
if (updateDeverOpen) updateDeverOpen();
var syncAdv = $(this).data("syncSenseAdv");
if (syncAdv) syncAdv();
});
// Sync tr/t sub-fields in every nimûne item to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-nimune-list li.wiktku-dfn-nimune-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it.
var subWrap = li.find(".wiktku-dfn-nimune-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-tr")
.attr("placeholder", "Transliterasyon…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-nimune-sub");
li.append(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 5)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-t")
.attr("placeholder", "Tercimeya nimûneyê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-nimune-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
// Re-sync the "<label> bi <ziman>…" placeholder on every always-visible
// sub-field list (etîket, kategorî, hevmane, …), since the language may
// not have been chosen yet when these lists were first built, or may
// have changed since.
$("#wiktku-start-dfn .wiktku-dfn-sublist").each(function () {
var upd = $(this).data("updatePlaceholders");
if (typeof upd === "function") upd();
});
// Same idea for word-list steps (jê, têkildar, binêre herwiha, …)
// whose placeholder is a function of the current language.
$.each(steps, function (stepId, step) {
if (typeof step.placeholder === "function") {
$("#wiktku-start-" + stepId + " textarea.wiktku-start-step-field")
.attr("placeholder", step.placeholder());
}
});
// Sync tr/t sub-fields in every jêder citation to the current language.
// tr shown when language needs transliteration; t shown when lang !== "ku".
(function () {
var lang = values.lang || "";
var showTr = needsTransliteration(lang);
var showT = lang && lang !== "ku";
$("#wiktku-start-dfn .wiktku-dfn-jeder-list li.wiktku-dfn-jeder-li").each(function () {
var li = $(this);
// Ensure the sub-wrapper exists when we need it. It must sit right
// after the quote textarea and before the source selector.
var subWrap = li.find(".wiktku-dfn-quote-sub");
// tr field
var trTa = li.find(".wiktku-dfn-nimune-quote-tr");
if (showTr && !trTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// Insert after the quote textarea's resize-wrap (if the manual
// resize handle has already been attached to it), not after the
// bare textarea -- otherwise this lands *inside* that wrap,
// between the textarea and its own grip, breaking jêgirtin's
// handle and nesting the tr/t boxes inside it.
var quoteTa = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap = quoteTa.closest(".wiktku-resize-wrap");
(quoteResizeWrap.length ? quoteResizeWrap : quoteTa).after(subWrap);
}
trTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-tr-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-tr")
.attr("placeholder", "Transliterasyona jêgirtinê…");
subWrap.prepend(trTa);
} else if (!showTr && trTa.length) {
trTa.remove();
}
// t field
var tTa = li.find(".wiktku-dfn-nimune-quote-t");
if (showT && !tTa.length) {
if (!subWrap.length) {
subWrap = $("<div></div>").addClass("wiktku-dfn-quote-sub");
// See the tr-field branch above for why this targets the
// resize-wrap instead of the bare textarea.
var quoteTa2 = li.find(".wiktku-dfn-nimune-quote");
var quoteResizeWrap2 = quoteTa2.closest(".wiktku-resize-wrap");
(quoteResizeWrap2.length ? quoteResizeWrap2 : quoteTa2).after(subWrap);
}
tTa = $("<textarea></textarea>")
.attr("name", "wiktku-dfn-nimune-quote-t-" + $.now() + "-" + Math.random().toString(36).substr(2, 9))
.attr("rows", 2)
.attr("class", "wiktku-dfn-subfield wiktku-dfn-nimune-quote-t")
.attr("placeholder", "Tercimeya jêgirtinê bi kurmancî…");
subWrap.append(tTa);
} else if (!showT && tTa.length) {
tTa.remove();
}
// Remove the wrapper if it ended up empty.
subWrap = li.find(".wiktku-dfn-quote-sub");
if (subWrap.length && !subWrap.children().length) subWrap.remove();
});
}());
$.each(steps, function (stepId, step) {
var groupIdx = stepIdToGroup[stepId];
var hiddenByTab = (groupIdx !== undefined && groupIdx !== activeGroup) ||
// Beşên din picker: stays hidden even while its tab is
// active until explicitly opened from the dropdown.
(BESENDIN_SET[stepId] && !openedOtherSections[stepId]);
// Bilêvkirin is its own section now, so the two views are
// mutually exclusive: on it, only pron shows (plus Ziman, which
// is shared and stays above every section); off it, pron is
// hidden no matter which tab is active. pron is deliberately
// ungrouped — stepIdToGroup has no entry for it — so without
// this branch it would show on all four tabs at once.
if (stepId === "pron") {
hiddenByTab = hiddenByTab || !viewingPron;
} else if (viewingPron && stepId !== "lang") {
hiddenByTab = true;
}
if (hiddenByTab || (typeof(step.visible) === "function" && !step.visible(values))) {
$("#wiktku-start-" + stepId).hide();
}
else {
var stepDiv = $("#wiktku-start-" + stepId);
if (changedStep !== step && typeof(step.build) === "function") {
prepareInput(step.build(stepDiv, values), step);
}
// After build, sync the posparams helper widgets whenever pos or lang changes.
if (step.id === "posparams" && typeof step.syncHelpers === "function") {
step.syncHelpers(stepDiv);
}
var input = stepDiv.find(".wiktku-start-step-field");
if (typeof(step.example) === "function") {
// .find() above already reaches nested fields (e.g. the dfn
// step's mane textareas inside each sense block), so one
// assignment covers every step; dfn keeps its own fallback.
var exampleText = step.example(values) || (step.id === "dfn" ? "Mane…" : "");
input.attr("placeholder", exampleText);
}
stepDiv.slideDown();
}
if (step.required &&
(values[stepId] === "" ||
(step.multiple && values[stepId].join("") === ""))) {
canContinue = false;
}
});
// The three content tabs belong to a POS section — Bilêvkirin has no
// sub-tabs of its own, so the bar goes away while it's on screen.
$("#wiktku-group-tabs").toggle(!!values.pos && !viewingPron);
var besenDinActive = activeGroup === ELEQEDAR_GROUP_IDX && !viewingPron;
$("#wiktku-besendin-picker").toggle(besenDinActive);
// The option list is filtered by each step's visible(), which depends
// on lang/pos — so it has to be rebuilt when those change, not just
// when a section is opened. Safe to call from here: rendering the
// picker doesn't itself call refresh(), only picking an option does.
if (besenDinActive) renderBesenDinPicker();
updateGroupTabIndicators(values);
continueBtn.setDisabled(!canContinue);
// Soft gate: stays a real, clickable OOUI button — a hard
// setDisabled(true) here would swallow the click entirely, leaving
// no way to tell the person why nothing happened. The CSS-only
// "looks disabled" class lets the click handler still run and show
// wiktkuNotify's explanation.
wiktkuContentGateOk = !canContinue || hasMinRequiredContent(values);
continueBtn.$element.toggleClass("wiktku-btn-soft-disabled", !wiktkuContentGateOk);
}
function destroy() {
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
if (autosaveInterval) clearInterval(autosaveInterval);
formDirty = false;
$(window).off("beforeunload.wiktkuUnsaved");
$("#wiktku-start-form, .noafterwizard").slideUp(function () {
$(this).remove();
});
$(".noarticletext .nowizard").slideDown();
// #editform was kept laid-out but off-screen (see build()) instead of
// display:none, precisely so the editor never had to size itself
// against a zero-width/height box. Just bring it back on-screen —
// no slideDown needed since it was never actually collapsed.
$("#editform").removeClass("wiktku-offscreen");
// Belt-and-braces: if WikiEditor/CodeMirror/mobile's auto-grow still
// cached a stale size from some earlier point, nudge them to
// recalculate now that the box is genuinely visible.
$(window).trigger("resize");
if (window.CodeMirror) {
var cm = $("#wpTextbox1").data("CodeMirror") || $("#wpTextbox1").data("cmInstance");
if (cm && typeof cm.refresh === "function") cm.refresh();
}
$("#ca-edit").removeClass("selected");
$("#ca-wiktku-nowizard").addClass("selected");
}
// Priority order for Kurdish dialects and navz.
// Codes listed here come before all other languages; unlisted codes
// are sorted alphabetically by their display name via localeCompare.
var LANG_PRIORITY = ["navz", "ku", "hac", "sdh", "lki", "ckb", "zza"];
// Inserts a new "== {{ziman|code}} ==" section at the correct sorted
// position: navz, then ku/hac/sdh/lki/ckb/zza, then alphabetical by name.
function insertNewLangSection(existing, wikitext) {
var newCodeMatch = wikitext.match(new RegExp("^" + langHeaderSource("[^}]+")));
var newCode = newCodeMatch ? newCodeMatch[1] : "";
var sections = [];
var preamble = "";
var anyHeaderSrc = langHeaderSource("[^}]+");
var firstMatch = new RegExp("^" + anyHeaderSrc, "m").exec(existing);
if (!firstMatch) {
// No existing lang sections — just write the new section.
// Add \n\n only when there is real non-whitespace content above.
var trimmed = existing.replace(/\s+$/, "");
return trimmed ? trimmed + "\n\n" + wikitext : wikitext;
}
// Everything before the first == {{ziman|...}} == is the preamble.
var firstSectionStart = firstMatch.index;
// Walk back to the start of that line.
var lineStart = existing.lastIndexOf("\n", firstSectionStart - 1);
var preambleEnd = lineStart !== -1 ? lineStart + 1 : 0;
preamble = existing.slice(0, preambleEnd);
// Parse each language section: header line + everything until the next header.
var body = existing.slice(preambleEnd);
// (?![\s\S]) matches only true end-of-string, since a bare "$" with
// /m would stop at the section's first line break instead.
var langSectionRe = new RegExp(
"^(" + anyHeaderSrc + "\\n?)([\\s\\S]*?)(?=\\n" + anyHeaderSrc + "|(?![\\s\\S]))",
"gm"
);
var m;
while ((m = langSectionRe.exec(body)) !== null) {
sections.push({
code: m[2],
content: m[0] // full text of this section including header
});
}
// Determine sort rank: priority list first (by index), then alphabetical.
function getLangName(code) {
var entry = langsByCode[code];
return entry ? entry.label : code;
}
function sortRank(code) {
var idx = LANG_PRIORITY.indexOf(code);
return idx !== -1 ? idx : LANG_PRIORITY.length;
}
function compareLangs(a, b) {
var ra = sortRank(a), rb = sortRank(b);
if (ra !== rb) return ra - rb;
// Both are outside priority list — sort by display name.
return getLangName(a).localeCompare(getLangName(b), userLang);
}
// Insert the new section into the sorted list.
var newSection = { code: newCode, content: wikitext };
var inserted = false;
for (var i = 0; i < sections.length; i++) {
if (compareLangs(newCode, sections[i].code) < 0) {
sections.splice(i, 0, newSection);
inserted = true;
break;
}
}
if (!inserted) sections.push(newSection);
// Re-join: preamble + sections, each separated by exactly \n\n.
// Trim trailing whitespace off each section's content first so we
// control the spacing precisely.
var joined = sections.map(function (s) {
return s.content.replace(/\s+$/, "");
}).join("\n\n");
// Only add a separator after the preamble when it has real content.
var preambleTrimmed = preamble.replace(/\s+$/, "");
var prefix = preambleTrimmed ? preambleTrimmed + "\n\n" : "";
return prefix + joined + "\n";
}
function build() {
$(".nowizard, .nowizard .noafterwizard").hide();
var callToAction = $("#wiktku-calltoaction");
var origCallToActionText = callToAction.text();
$(mw.util.addPortletLink("p-views", "#", "Edîtora çavkaniyê veke",
"ca-wiktku-nowizard", "Vê rûpelê bi edîtora çavkaniyê çêke",
undefined, "#ca-watch")).click(function (evt) {
evt.preventDefault();
callToAction.text(origCallToActionText);
destroy();
});
if (callToAction.length) {
callToAction.text(origCallToActionText +
" Forma li jêr ji bo çêkirina madeyeke nû bi kar bîne.");
}
var startForm = $("<form id='wiktku-start-form'></form>");
var clearPageCheck = null;
if (addLangMode) {
startForm.append($("<p class='wiktku-addlang-notice'></p>").html(
"Bi vê formê tu yê zimanekî nû li rûpelê îlawe bikî. Eger ev ziman jixwe hebe, beşa nû li dawiya vî zimanî dê were îlawekirin. " +
"Ji kerema xwe netîceya dawî kontrol bike, eger problemek derkeve, li <a href='" +
mw.util.getUrl('Mediawiki talk:Gadget-start.js') +
"'>nîqaşa vê amûrê</a> problemê ragihîne."
));
clearPageCheck = $("<input type='checkbox' id='wiktku-clear-page-check'>");
var clearPageLabel = $("<label for='wiktku-clear-page-check'></label>")
.text(" Muhtewaya rûpelê paqij bike (her tiştî jê bibe û rûpelê ji nû ve binivîse)");
startForm.append(
$("<p class='wiktku-addlang-notice'></p>")
.append(clearPageCheck)
.append(clearPageLabel)
);
// Re-evaluate step visibility (e.g. the pron step) whenever this
// is toggled, since it changes what counts as "existing content".
clearPageCheck.on("change", function () {
refresh();
});
}
var spinner;
// Tab bar for switching between POS sections.
var sectionTabsDiv = $("<div id='wiktku-section-tabs'></div>");
// Tab bar for switching between step-groups (topics) within a section.
// renderGroupTabs()/updateGroupTabIndicators() (which populate and
// maintain it) live outside build() — see near "var activeSection".
var groupTabsDiv = $("<div id='wiktku-group-tabs'></div>");
// Both tab bars are sticky (CSS). group-tabs' sticky "top" has to sit
// right below section-tabs' actual rendered height, which isn't a
// fixed number since the bar can wrap onto a second line. Measured
// here and published as a CSS custom property the stylesheet reads
// back; recomputed on content changes and window resize.
//
// Vector 2022 has its own sticky header (.vector-sticky-header) that
// pins to the real top:0 once you scroll past the page title, which
// would otherwise sit directly behind (and hide) our section-tabs.
// Fixed by measuring how much of the viewport Vector's header
// currently occupies and offsetting both our bars below it, via
// --wiktku-siteheader-h. Vector's header slides in/out with scroll
// position, so this is re-measured on scroll too, not just resize.
function siteHeaderOffset() {
var el = document.querySelector(".vector-sticky-header");
if (!el) return 0;
var rect = el.getBoundingClientRect();
// Present in the DOM at all times on Vector 2022, but only
// actually occupying screen space once scrolled into place —
// before that it sits translated above the viewport (negative/
// zero rect), which must not add a phantom gap under our bars.
if (rect.bottom <= 0 || rect.top > 4) return 0;
return Math.max(0, rect.bottom);
}
var stickyOffsetQueued = false;
function syncStickyTabsOffset() {
var h = sectionTabsDiv.is(":visible") ? (sectionTabsDiv.outerHeight() || 0) : 0;
startForm.css("--wiktku-stabs-h", h + "px");
startForm.css("--wiktku-siteheader-h", siteHeaderOffset() + "px");
}
function queueStickyTabsOffsetSync() {
if (stickyOffsetQueued) return;
stickyOffsetQueued = true;
window.requestAnimationFrame(function () {
stickyOffsetQueued = false;
syncStickyTabsOffset();
});
}
$(window).on("resize.wiktkuStickyTabs scroll.wiktkuStickyTabs", queueStickyTabsOffsetSync);
// "Beşên din" picker — sits right under the group tab bar, only
// shown while the Eleqedar tab is active (toggled in refresh(), see
// ELEQEDAR_GROUP_IDX); renderBesenDinPicker() fills it.
var besenDinPickerDiv = $("<div id='wiktku-besendin-picker' class='wiktku-besendin-picker'></div>");
/** Puts the form back in a usable state after a failed submit and
* says what happened. Without this the button stays disabled and
* the spinner spins forever on any exception — the person is left
* staring at a form that looks busy but is dead, with the reason
* visible only in the browser console. */
function submitFailed(err) {
spinner.hide();
continueBtn.setDisabled(false);
continueBtn.$element.removeClass("wiktku-btn-sending");
var detail = (err && (err.message || err.toString())) || "sedem nediyar";
mw.notify(
"Şandin bi ser neket: " + detail +
" — tiştê ku te nivîsî winda nebûye. Heke ev dubare bibe, " +
"bi 'Edîtora çavkaniyê veke' berdewam bike.",
{ type: "error", tag: "wiktku-submit", autoHide: false }
);
// Deliberately console.error as well: the notification is for the
// person, this is for whoever they report the bug to.
if (window.console && console.error) console.error("[wiktku] submit failed", err);
}
onSubmitError = submitFailed;
startForm.submit(function (evt) {
evt.preventDefault();
continueBtn.setDisabled(true);
continueBtn.$element.addClass("wiktku-btn-sending");
spinner.show();
try {
getWikitext(function (wikitext) {
try {
if (addLangMode && clearPageCheck && clearPageCheck.prop("checked")) {
// User asked to wipe the page — write fresh content.
$("#wpTextbox1").val(wikitext);
} else if (addLangMode) {
var textbox = $("#wpTextbox1");
var existing = textbox.val() || "";
// ── h-bnr merge ──────────────────────────────────────────
// If the user filled bnr-h inputs, either merge into the
// existing {{h-bnr|…}} call or prepend a new one.
var bnrhOut = steps.bnrh.output(getValues());
if (bnrhOut) {
var newPages = bnrhOut.replace(/^\{\{h-bnr\|/, "").replace(/\}\}\n?$/, "").split("|");
var bnrhRe = /\{\{h-bnr\|([^}]*)\}\}/i;
if (bnrhRe.test(existing)) {
existing = existing.replace(bnrhRe, function (_, inner) {
var existingPages = inner.split("|").map(function (p) { return p.trim(); });
$.each(newPages, function (_, p) {
p = p.trim();
if (p && existingPages.indexOf(p) === -1) existingPages.push(p);
});
return "{{h-bnr|" + existingPages.join("|") + "}}";
});
} else {
existing = bnrhOut + existing;
}
}
// If this language's section already exists (whitespace
// differences aside), merge into it; otherwise insert new.
var langHeaderMatch = wikitext.match(new RegExp("^(" + langHeaderSource("[^}]+") + "\\n)"));
if (langHeaderMatch) {
var langHeader = langHeaderMatch[1]; // e.g. "== {{ziman|ku}} ==\n"
var newCode = langHeaderMatch[2];
var existingHeader = findLangHeader(existing, newCode);
if (existingHeader) {
// Strip the header from the new output.
var newContent = wikitext.slice(langHeader.length);
// Find the insertion point: end of the existing lang section.
// A new L2 heading marks the start of the next language block.
var searchFrom = existingHeader.index + existingHeader.match.length;
// Find the next == … == heading after this section's start.
var nextL2 = existing.indexOf("\n== ", searchFrom);
var insertAt = nextL2 !== -1 ? nextL2 : existing.length;
// Ensure there's a blank line before the inserted block.
var before = existing.slice(0, insertAt);
var after = existing.slice(insertAt);
var sep = /\n\s*$/.test(before) ? "" : "\n";
textbox.val(before + sep + newContent + after);
} else {
// New language — insert at the correct sorted position.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
// Unexpected format — insert at sorted position anyway.
textbox.val(insertNewLangSection(existing, wikitext));
}
} else {
$("#wpTextbox1").val(wikitext);
}
$("html, body").animate({scrollTop: 0});
callToAction.text("Hindik ma!");
// The draft is now live in the editor, submitted or not — keeping
// it cached would just offer to restore already-used content
// the next time the wizard opens on this page.
try { formCache.Set({}); } catch (e) { /* storage unavailable — harmless, the content is already in the editor */ }
destroy();
} catch (e) {
// Thrown while writing the result into #wpTextbox1.
submitFailed(e);
}
});
} catch (e) {
// Catches anything thrown synchronously inside getWikitext.
submitFailed(e);
}
});
// Tracks whether groupTabsDiv has been inserted yet.
var groupTabsInserted = false;
$.each(ordered_steps, function (i, step) {
var stepDiv = $(mw.html.element("div", {
"class": "wiktku-start-step",
id: "wiktku-start-" + step.id }));
var name = capitalize(step.name);
var nameH3 = $("<h3></h3>").addClass("wiktku-start-step-name");
var nameLabel = $("<label></label>")
.attr("for", "wiktku-start-" + step.id + "-field")
.text(name);
nameH3.append(nameLabel);
stepDiv.append(nameH3);
if (step.question_fmt) {
// question_fmt can be a plain string, or a function of the
// current values (used by steps whose question text depends
// on e.g. the chosen language) — refresh() keeps it in sync
// afterwards via the .wiktku-start-step-question class.
var questionText = typeof step.question_fmt === "function"
? step.question_fmt(getValues())
: step.question_fmt;
var questionLabel = $(mw.html.element("label", {
"class": "wiktku-start-step-question",
"for": "wiktku-start-" + step.id + "-field" }, questionText));
stepDiv.append(questionLabel);
}
var input;
if (step.choices) {
input = $(mw.html.element("select", {
"class": "wiktku-start-step-field",
id: "wiktku-start-" + step.id + "-field" }));
input.attr("data-placeholder",
capitalize(step.name) + " hilbijêre ");
populateMenus(input, step.choices);
if (step.multiple) input.attr("multiple", true);
// initSelects() runs over the whole form once it is in the DOM.
stepDiv.append(input);
// Pos-specific: the definition/example for whichever cureyê
// rêzimanî is currently chosen, as plain text right under the
// dropdown — kept in sync by refresh() via .wiktku-start-pos-example.
if (step.id === "pos") {
stepDiv.append(
$("<p class='wiktku-step-hint wiktku-start-pos-example'></p>").text(step.example(getValues()))
);
}
} else if (typeof(step.build) === "function") {
input = step.build(stepDiv);
} else {
input = $("<textarea></textarea>")
.attr("class", "wiktku-start-step-field")
.attr("id", "wiktku-start-" + step.id + "-field")
.attr("rows", step.rows || 2);
if (step.narrow) input.addClass("wiktku-start-narrow");
if (step.placeholder) {
input.attr("placeholder", typeof step.placeholder === "function" ? step.placeholder() : step.placeholder);
}
if (step.multiple) {
input.on("input", function (evt) {
refreshForTextField(evt.target, step);
});
var keyInput = buildKeyInput(step);
// Same one-box-per-word layout as the per-sense ⊕-modifier
// groups (see makeModifierLi) when this step has modifiers.
var list = $(step.ordered ? "<ol></ol>" : "<ul></ul>")
.append(step.modifiers
? makeModifierLi(input)
: $("<li></li>").append(keyInput).append(input));
stepDiv.append(list);
}
else stepDiv.append(input);
}
prepareInput(input, step);
stepDiv.hide();
// Section tabs (switch between POS sections) sit above the
// per-section fields, starting at "pos".
if (step.id === "pos") {
startForm.append(sectionTabsDiv);
}
// Step-group tabs sit just before the first grouped step in
// ordered_steps. Derived from stepIdToGroup so this keeps
// working no matter how STEP_GROUPS/ordered_steps get reordered.
if (!groupTabsInserted && stepIdToGroup[step.id] !== undefined) {
startForm.append(groupTabsDiv);
startForm.append(besenDinPickerDiv);
groupTabsInserted = true;
}
startForm.append(stepDiv);
});
// ── Section tab helpers ──────────────────────────────────────────────
// ── Add-section button — lives in the tab bar, disabled until pos chosen ──
var addSectionBtn = new OO.ui.ButtonWidget({
label: "+ Beşeke din",
disabled: true,
classes: [ "wiktku-add-sense-btn" ]
});
/** Adds a new, empty POS section and switches to it. */
function addPosSection() {
saveCurrentSection();
viewingPron = false;
sections.push({});
activeSection = sections.length - 1;
$.each(PER_SECTION_STEP_IDS, function (_, id) {
// Declared once here rather than with `var` in the "dfn"
// branch and again in the fallback branch below — each
// branch returns before the other runs, but both are
// lexically in this same function scope.
var stepDiv;
if (id === "dfn") {
stepDiv = $("#wiktku-start-dfn");
stepDiv.find(".wiktku-dfn-sense-tabs").remove();
stepDiv.find("ol.wiktku-dfn-list").remove();
steps.dfn.build(stepDiv);
return;
}
if (id === "etym") {
$("#wiktku-start-etym .wiktku-etym-wrap").remove();
steps.etym.build($("#wiktku-start-etym"));
return;
}
if (id === "cavkani") {
$("#wiktku-start-cavkani .wiktku-cavkani-wrap").remove();
steps.cavkani.build($("#wiktku-start-cavkani"));
return;
}
if (id === "posparams") {
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-wrap").remove();
return;
}
if (id === "resim") {
$("#wiktku-start-resim .wiktku-resim-wrap").remove();
return;
}
if (id === "trans") {
var transDiv = $("#wiktku-start-trans");
transDiv.find(".wiktku-trans-group").remove();
return;
}
if (id === "zarava") {
var zaravaDiv = $("#wiktku-start-zarava");
zaravaDiv.find(".wiktku-zarava-group").remove();
return;
}
if (id === "dunde") {
var dundeDiv = $("#wiktku-start-dunde");
dundeDiv.find("ul.wiktku-dunde-list").remove();
return;
}
stepDiv = $("#wiktku-start-" + id);
stepDiv.find(".wiktku-start-step-field").val("");
stepDiv.find("select.wiktku-start-step-field").trigger("wiktku:refresh");
});
// Fresh section — no "Beşên din" sub-section has been opened yet.
openedOtherSections = {};
renderBesenDinPicker();
renderSectionTabs();
refresh();
}
addSectionBtn.on("click", addPosSection);
/** Removes one POS section. Assumes the caller already confirmed and
* already checked more than one section remains. If the removed
* section was the one on screen, its in-progress DOM edits are
* discarded along with it (never saved back into sections[]) and a
* neighboring section is restored in its place. */
function removePosSection(idx) {
sections.splice(idx, 1);
if (idx === activeSection) {
viewingPron = false;
activeSection = Math.min(idx, sections.length - 1);
restoreSection(sections[activeSection]);
refresh();
} else if (idx < activeSection) {
activeSection -= 1;
}
renderSectionTabs();
}
/** Re-render the tab bar to reflect current sections[] and activeSection. */
function renderSectionTabs() {
// Detach (not remove) the button first: .empty() strips bound
// event data from any children left inside it, which would
// silently kill addSectionBtn's click handler on every re-render.
addSectionBtn.$element.detach();
sectionTabsDiv.empty();
var lang = getValues().lang || "";
if (!lang) {
sectionTabsDiv.hide();
syncStickyTabsOffset();
return;
}
sectionTabsDiv.show();
// Bilêvkirin leads the bar, ahead of the POS sections.
var pronTab = $("<span></span>")
.addClass("wiktku-section-tab wiktku-section-tab-pron")
.text("Bilêvkirin")
.toggleClass("wiktku-tab-active", viewingPron);
pronTab.on("click", function () {
if (viewingPron) return;
saveCurrentSection();
viewingPron = true;
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
sectionTabsDiv.append(pronTab);
$.each(sections, function (i) {
var posVal = sections[i].pos || "";
var tabLabel = posVal ? capitalize(posVal) : ("Beş " + (i + 1));
var tab = $("<span></span>")
.addClass("wiktku-section-tab")
.text(tabLabel)
.toggleClass("wiktku-tab-active", i === activeSection && !viewingPron);
(function (idx) {
tab.on("click", function () {
if (idx === activeSection && !viewingPron) return;
// Leaving Bilêvkirin doesn't need a save — its field
// is shared and never part of a section snapshot.
if (!viewingPron) saveCurrentSection();
viewingPron = false;
activeSection = idx;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
scrollTabBarIntoView("wiktku-section-tabs");
});
}(i));
sectionTabsDiv.append(tab);
});
// One delete button for the whole bar — not one per tab — same
// reasoning as the Mane tabs' trash button: deletes whichever
// Beş is currently open, so there's only one thing a click on it
// can possibly remove. Not shown on Bilêvkirin (nothing to
// delete there) or when it's the only Beş left.
if (sections.length > 1 && !viewingPron) {
var activeTabLabel = sections[activeSection] && sections[activeSection].pos
? capitalize(sections[activeSection].pos)
: ("Beş " + (activeSection + 1));
var sectionTrash = makeTrashButton("Vê beşê jê bibe (" + activeTabLabel + ")");
sectionTrash.on("click", function () {
if (!window.confirm("Tu dixwazî \"" + activeTabLabel + "\" jê bibî? Ev tişt nayê vegerandin.")) return;
removePosSection(activeSection);
});
sectionTabsDiv.append(sectionTrash.$element);
}
// Enable "+ Beşeke din" only when the active section has a pos chosen.
var activePos = sections[activeSection] && sections[activeSection].pos
? sections[activeSection].pos
: getValues().pos;
addSectionBtn.setDisabled(!activePos);
sectionTabsDiv.append(addSectionBtn.$element);
syncStickyTabsOffset();
}
renderSectionTabs();
/**
* Re-populates a dynamically-growing "multiple" field list (bnrh, je,
* tekildar, …) from a saved array of strings. Sets each value via
* .val() + a triggered "input" event rather than touching the DOM
* structure directly, so the normal refreshForTextField() growth
* logic adds exactly as many rows as needed — the same thing that
* happens when a person types into the last (empty) row.
*/
function restoreMultiField(id, values) {
var container = $("#wiktku-start-" + id);
$.each(values || [], function (i, v) {
var field = container.find(".wiktku-start-step-field").eq(i);
if (!field.length || !v) return;
field.val(v).trigger("input");
});
}
/**
* Restores a full cached draft: shared lang/pron/bnrh fields, then
* every section (via the same restoreSection()/refresh()/
* renderSectionTabs() sequence already used when switching tabs).
* @param cached {{lang: string, pron: string, bnrh: Array, sections: Array, activeSection: number}}
*/
function restoreFromCache(cached) {
if (cached.lang) {
$("#wiktku-start-lang-field").val(cached.lang);
$("#wiktku-start-lang-field").trigger("wiktku:refresh");
}
// Rebuild dependent steps (pos, pron wrap, etc.) now that lang is set.
refresh();
if (cached.pron !== undefined) {
$("#wiktku-start-pron .wiktku-pron-text").val(cached.pron);
}
if (cached.bnrh) {
restoreMultiField("bnrh", cached.bnrh);
}
sections = (cached.sections && cached.sections.length) ? cached.sections : [{}];
activeSection = (typeof cached.activeSection === "number" &&
cached.activeSection >= 0 && cached.activeSection < sections.length)
? cached.activeSection : 0;
restoreSection(sections[activeSection]);
refresh();
renderSectionTabs();
updateLastSavedIndicator(cached.savedAt || null);
formDirty = false;
wiktkuNotify("Danûstandina berê ya qeydkirî ji cache hate barkirin.",
{ type: "info", tag: "wiktku-cache-restore" });
}
// Update the active tab's label whenever pos changes.
startForm.on("change", "#wiktku-start-pos-field", function () {
// renderSectionTabs() labels each tab from sections[i].pos, which
// is only synced from the DOM by saveCurrentSection() (on tab
// switch/section add). Without this, the active tab's own label
// stays on the previous pos (or "Beş N") until the user switches
// away and back. Update it directly so the rename is immediate.
if (sections[activeSection]) sections[activeSection].pos = $(this).val();
renderSectionTabs();
// A new cureyê rêzimanî means any previously chosen cinsiyet/
// cureyê lêkerê no longer applies — clear both selections (and
// the shared hidden cinsiyet field, since rebuildTa()/syncHelpers()
// fall back to it) before wiping the textarea, so nothing stale
// gets carried over into the rebuilt template.
var ppDiv = $("#wiktku-start-posparams");
ppDiv.find(".wiktku-posparams-cinsiyet-sel").val("");
ppDiv.find(".wiktku-posparams-jimar-sel").val("");
ppDiv.find(".wiktku-posparams-lektur-sel").prop("selectedIndex", 0);
ppDiv.find(".wiktku-posparams-lekturform-sel").prop("selectedIndex", 0);
$("#wiktku-start-cinsiyet-field").val("");
$("#wiktku-start-jimar-field").val("");
ppDiv.find(".wiktku-posparams-text").val("");
// refresh() runs steps.posparams.syncHelpers(), which toggles the
// param rows for the new pos and — since the textarea is now
// empty — calls rebuildTa() to repopulate it.
refresh();
});
// Show the tab bar as soon as a language is selected.
startForm.on("change", "#wiktku-start-lang-field", function () {
renderSectionTabs();
});
var footer = $("<div id='wiktku-start-footer'></div>");
spinner = $(mw.html.element("img", {
id: "wiktku-start-spinner",
src: "//upload.wikimedia.org/wikipedia/commons/d/de/Ajax-loader.gif",
alt: "Tê barkirin…",
title: "Tê barkirin…" }));
footer.append(spinner.hide());
continueBtn = new OO.ui.ButtonWidget({
id: "wiktku-unhide-editform",
label: "Bişîne",
flags: [ "primary", "progressive" ]
});
continueBtn.on("click", function () {
// Immediate feedback that the click registered
continueBtn.$element.addClass("wiktku-btn-flash");
setTimeout(function () {
continueBtn.$element.removeClass("wiktku-btn-flash");
}, 500);
// Ziman/Cureyê rêzimanî already block submission via the real
// OOUI setDisabled (see canContinue in refresh()) — this is a
// second, softer check that still lets the click through so we
// can explain why, instead of the button just silently doing nothing.
if (!wiktkuContentGateOk) {
wiktkuNotify(
"Berî şandinê, tiştekî binivîse: Mane, Bi zaravayên din, an jî Werger — kêmasî yek ji van sê beşan divê tijî be.",
{ type: "warn", tag: "wiktku-mincontent" }
);
return;
}
wiktkuNotify("Tê şandin…", { type: "info", tag: "wiktku-submit" });
startForm.trigger("submit");
});
footer.append(continueBtn.$element);
// "Edîtora çavkaniyê veke" button — inline fallback for the portlet link
var sourceEditorBtn = new OO.ui.ButtonWidget({
label: "Edîtora çavkaniyê veke",
flags: [ "destructive" ],
});
sourceEditorBtn.on("click", function () {
destroy();
});
footer.append(sourceEditorBtn.$element);
// ── Cache save/clear buttons ─────────────────────────────────────────
// Manual, page-scoped save of the entire wizard (all sections, not
// just the active one) via [[MediaWiki:Gadget-StorageUtils.js]]'s
// ObjectStorage. Restoring happens silently on load at the end of
// build(), once the full language list is available.
//
// Saving also happens automatically (debounced while typing, plus a
// periodic fallback) — see "Autosave" below — since relying on the
// person to remember to click "Li cache qeyd bike" before an
// accidental tab close/reload defeats the point of having a cache
// at all.
function buildCacheSnapshot() {
return {
lang: getValues().lang || "",
pron: $("#wiktku-start-pron .wiktku-pron-text").val() || "",
// bnrh is shared (page-level), same as lang/pron above — not
// per-section, so it doesn't live in sections[].
bnrh: getValuesForStep("bnrh"),
sections: sections,
activeSection: activeSection,
savedAt: Date.now()
};
}
// ── Last-saved indicator ─────────────────────────────────────────────
var lastSavedEl = $("<span id='wiktku-last-saved'></span>");
function formatSavedTime(ts) {
try {
return new Date(ts).toLocaleTimeString(mw.config.get("wgUserLanguage"),
{ hour: "2-digit", minute: "2-digit" });
} catch (e) {
return new Date(ts).toLocaleTimeString();
}
}
function updateLastSavedIndicator(ts) {
lastSavedEl.text(ts ? ("Cache: qeydkirî di saet " + formatSavedTime(ts) + " de") : "");
}
// ── Dirty-state tracking + autosave ──────────────────────────────────
// formDirty mirrors "does the live form differ from what's in cache".
// Anything the person types/selects sets it; a successful save (auto
// or manual) clears it; destroy() (final submit, or switching to the
// source editor) also clears it since the wizard's job is over at
// that point either way. formDirty/autosaveDebounceTimer/autosaveInterval
// are declared in the outer scope (near activeGroup) — assigned here,
// not re-declared — so destroy() can reach them too.
var AUTOSAVE_DEBOUNCE_MS = 8000; // save shortly after typing pauses
var AUTOSAVE_INTERVAL_MS = 60000; // …and at least this often regardless
function performAutosave() {
if (!formDirty) return;
saveCurrentSection();
try {
formCache.Set(buildCacheSnapshot());
formDirty = false;
updateLastSavedIndicator(Date.now());
} catch (e) {
// Silent on autosave failure (storage blocked/full, etc.) — the
// manual button still surfaces a proper error if the person
// tries it directly, no need to interrupt typing with a toast.
}
}
function scheduleAutosave() {
formDirty = true;
if (autosaveDebounceTimer) clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = setTimeout(performAutosave, AUTOSAVE_DEBOUNCE_MS);
}
startForm.on("input change", "input, textarea, select", scheduleAutosave);
autosaveInterval = setInterval(performAutosave, AUTOSAVE_INTERVAL_MS);
// Warn on accidental tab close/reload while there's anything unsaved.
$(window).on("beforeunload.wiktkuUnsaved", function (e) {
if (!formDirty) return undefined;
e.preventDefault();
e.returnValue = "";
return "";
});
var saveCacheBtn = new OO.ui.ButtonWidget({
label: "Li cache qeyd bike",
title: "Halê vê formê yê niha (ziman, bilêvkirin û hemû beş) li cache (localStorage) qeyd bike, da ku eger rûpel bêyî xwestin were girtin an ji nû ve were barkirin, tu karibî berdewam bikî."
});
saveCacheBtn.on("click", function () {
saveCurrentSection();
// ObjectStorage.Set() doesn't return a success/failure value, so
// the only way to detect a real failure (storage blocked, quota
// exceeded, JSON.stringify choking on something unexpected) is to
// catch it here rather than inspect a (nonexistent) return value.
try {
var snapshot = buildCacheSnapshot();
formCache.Set(snapshot);
formDirty = false;
updateLastSavedIndicator(snapshot.savedAt);
wiktkuNotify("Halê formê li cache hate qeydkirin.", { type: "success", tag: "wiktku-cache-save" });
} catch (e) {
mw.notify("Qeydkirina li cache bi ser neket.", { type: "error", tag: "wiktku-cache-save" });
}
});
var clearCacheBtn = new OO.ui.ButtonWidget({
label: "Cache paqij bike",
flags: [ "destructive" ],
title: "Datayên qeydkirî yên vê rûpelê ji cache jê bibe (forma ku niha xuya dike naguherîne)."
});
clearCacheBtn.on("click", function () {
formCache.Set({});
// Cancel any autosave already scheduled from typing before this
// click — left alone, it fires a few seconds later (or the
// unconditional periodic autosave does, within a minute) using
// the still-unblanked live form, silently re-writing the very
// cache just cleared. That's why "clear cache, refresh later"
// could still show the old data. Not marking the form dirty here
// means autosave stays off until the person actually edits
// something again, at which point it resumes normally.
if (autosaveDebounceTimer) {
clearTimeout(autosaveDebounceTimer);
autosaveDebounceTimer = null;
}
formDirty = false;
updateLastSavedIndicator(null);
wiktkuNotify("Cache hate paqijkirin.", { type: "success", tag: "wiktku-cache-clear" });
});
// ── "⋯" overflow ─────────────────────────────────────────────
// One primary action and one secondary stay on the bar; everything
// else moves in here, to avoid four same-weight buttons crowding
// (and wrapping to three rows on a phone).
var moreBtn = $("<button type='button' class='wiktku-more-btn' title='Vebijarkên din'>⋯</button>");
var moreMenu = $("<div class='wiktku-more-menu'></div>").hide();
/** Moves an existing OOUI button into the overflow menu, keeping its
* handlers — the widgets already carry the behaviour, so this is
* purely about where they are shown. */
function intoMenu(btn, extraClass) {
var row = $("<div class='wiktku-more-item'></div>");
if (extraClass) row.addClass(extraClass);
btn.$element.on("click", function () { moreMenu.hide(); });
row.append(btn.$element);
moreMenu.append(row);
}
intoMenu(saveCacheBtn);
intoMenu(clearCacheBtn, "wiktku-more-item-danger");
moreMenu.append($("<div class='wiktku-more-sep'></div>"));
moreMenu.append($("<div class='wiktku-more-item wiktku-more-note'></div>").append(lastSavedEl));
moreBtn.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
var open = moreMenu.is(":visible");
moreMenu.toggle(!open);
if (!open) {
setTimeout(function () {
$(document).one("click.wiktkuMore", function () { moreMenu.hide(); });
}, 0);
}
});
var moreWrap = $("<span class='wiktku-more-wrap'></span>").append(moreBtn).append(moreMenu);
footer.append(moreWrap);
// Icon-only settings button — opens the Tercîh dialog. Plain OOUI
// icon names depend on which icon-pack module happens to be loaded,
// so this uses the actual Codex "settings" (color-subtle) SVG from
// Commons directly, the same way the spinner above hotlinks its gif.
var prefsBtn = $("<button></button>", {
type: "button",
"class": "wiktku-prefs-btn",
title: "Tercîh — sazkariyên vê amûrê biguherîne"
});
prefsBtn.on("click", function (e) {
e.preventDefault();
wiktkuOpenPrefsDialog();
});
// The gear is icon-only on the bar; inside a text menu it needs a
// label like everything else around it.
prefsBtn.on("click", function () { moreMenu.hide(); });
moreMenu.prepend($("<div class='wiktku-more-sep'></div>"));
moreMenu.prepend($("<div class='wiktku-more-item wiktku-more-item-prefs'></div>")
.append(prefsBtn)
.append($("<span class='wiktku-more-item-label'></span>").text("Tercîh")));
startForm.append(footer);
// NOTE: deliberately NOT .hide() (display:none). WikiEditor/CodeMirror/
// the mobile editor's auto-grow logic measure #wpTextbox1's real
// dimensions during their own init, which happens while the wizard is
// up. A display:none ancestor reports 0 for offsetWidth/offsetHeight/
// scrollHeight, so those scripts bake in a near-zero height that never
// gets recalculated later — slideDown() in destroy() only animates the
// outer box, it doesn't re-trigger their sizing. Pushing #editform
// off-screen instead keeps it laid out (real width/height available to
// measure) without being visible, so the editbox opens at full size.
$("#editform").addClass("wiktku-offscreen").before(startForm);
// renderGroupTabs() looks up #wiktku-group-tabs via a live DOM
// selector, so it must run after startForm is actually in the document.
renderGroupTabs();
renderBesenDinPicker();
// One pass over the whole form: every select that hasn't already been
// initialised, and isn't opted out with .wiktku-plain-select, gets
// the searchable picker.
initSelects(startForm);
refresh();
// Give every draggable textarea a custom resize grip (works
// consistently on Safari/iOS, unlike the native corner grip — see
// the "Manual drag-to-resize handle" comment in styles.css), and
// keep applying it to textareas added later on (new sense blocks,
// quote-source fields, nimûne rows, …).
wiktkuInitManualResize(startForm);
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
$(mutation.addedNodes).each(function () {
if (this.nodeType !== 1) return;
wiktkuInitManualResize(this);
});
});
}).observe(startForm[0], { childList: true, subtree: true });
// Now that the full language list is loaded, silently restore any
// draft the editor previously saved for this page.
var cachedDraft = formCache.Get();
if (cachedDraft && cachedDraft.sections && cachedDraft.sections.length) {
restoreFromCache(cachedDraft);
}
}
// Only oojs-ui-core blocks the initial render now — icons-moderation
// (the trash glyph) is fetched lazily by makeTrashButton() instead, so
// slow connections aren't held up loading it before anyone needs it.
mw.loader.using(["oojs-ui-core"], function () {
function wiktkuProceedWithData(wiktkuData) {
var d = wiktkuData || {};
// Populate metadata.
if (d.metadata) $.extend(metadata, d.metadata);
// Populate gender labels.
if (d.genderLabels) $.extend(GENDER_BASE_LABELS, d.genderLabels);
// Jimar (number) code definitions
if (d.numberLabels) $.extend(NUMBER_LABELS, d.numberLabels);
// Populate lang choices (pinned list).
if (d.langChoices) steps.lang.choices = choicesFromObject(d.langChoices, true);
// Populate langsByCode and merge full language list from d.ziman.
// d.ziman is a {code: name} object with all known languages.
$.each(steps.lang.choices, function (i, choice) {
if (choice.sortKey === undefined) choice.sortKey = choice.label;
langsByCode[choice.code] = choice;
});
if (d.ziman) {
var pinnedCodes = {};
$.each(steps.lang.choices, function (i, c) { pinnedCodes[c.code] = true; });
var extraChoices = [];
$.each(d.ziman, function (code, name) {
if (/-pro$/i.test(code)) return;
if (/ziman[eê]\s+destan/i.test(name)) return;
if (pinnedCodes[code]) return;
var choice = { code: code, label: name, sortKey: name };
extraChoices.push(choice);
langsByCode[code] = choice;
});
// One Intl.Collator built once and reused for the whole sort, instead of localeCompare() rebuilding a collator on every pair -- same order, far faster over ~3700 entries.
var langCollator = new Intl.Collator(userLang);
extraChoices.sort(function (a, b) {
return langCollator.compare(a.sortKey, b.sortKey);
});
steps.lang.choices = steps.lang.choices.concat(extraChoices);
}
// Keep the full merged list around for trans/dunde/zarava — those
// should still be able to reference any language even when the
// Tercîh panel restricts the main "ziman" step below — then, if
// the user set a restriction there, narrow steps.lang.choices
// itself to just those codes.
var wiktkuFullLangChoices = steps.lang.choices;
if (wiktkuPrefs.langCodes && wiktkuPrefs.langCodes.length) {
var wiktkuAllowedLangSet = {};
$.each(wiktkuPrefs.langCodes, function (i, code) { wiktkuAllowedLangSet[code] = true; });
var wiktkuRestrictedChoices = $.grep(wiktkuFullLangChoices, function (c) {
return !!wiktkuAllowedLangSet[c.code];
});
// Fall back to the full list instead of showing an empty
// dropdown if none of the saved codes matched anything
// (typo'd code, data.json changed since, …).
steps.lang.choices = wiktkuRestrictedChoices.length ? wiktkuRestrictedChoices : wiktkuFullLangChoices;
}
// Populate POS choices — also a plain {code: label} object in data.json.
if (d.posChoices) steps.pos.choices = choicesFromObject(d.posChoices, false);
// Populate template definitions for the dfn sense-block builder.
if (d.tplDefs) steps.dfn.tplDefs = d.tplDefs;
// Populate pos placeholder examples.
if (d.posExamples) steps.pos.posExamples = d.posExamples;
// Populate citation template definitions for the quote widget.
if (d.jederDefs) steps.dfn.jederDefs = d.jederDefs;
// Populate pronunciation and etymology template insert lists.
if (d.pronTpls) {
steps.pron.pronTpls = d.pronTpls;
steps.pron.refreshTplSelect();
}
if (d.etymTpls) {
steps.etym.etymTpls = d.etymTpls;
steps.etym.refreshTplSelect();
}
// Sync trans/dunde/zarava keys with the full language list
// (not the possibly Tercîh-restricted steps.lang.choices).
steps.trans.keys = wiktkuFullLangChoices;
steps.dunde.keys = wiktkuFullLangChoices;
steps.zarava.keys = getZaravaKeys(wiktkuFullLangChoices);
build();
}
// data.json ships inside this package module, so it is already parsed
// and in memory — no action=raw round trip, and no Testing switch to
// forget to flip back before deploying.
wiktkuProceedWithData(require("./start/data.json"));
});
});
//</nowiki>
jhycq816r6nhpnaqm216sryyee9xt59
Şablon:J:Mem û Zîn:1692/belge
10
1358422
6334415
6328162
2026-08-17T22:51:20Z
Wikihez
7344
/* Lîste */
6334415
wikitext
text/x-wiki
{{belgekirin/binrûpel}}
== Bikaranîn ==
<syntaxhighlight lang="wikitext" copy>
{{J:Mem û Zîn:1692|jêgirtin= |sernav= |amadekar= |weşanxane= |sala weşandinê= |çap= |cild= |rûpel= |isbn= |url= }}
</syntaxhighlight>
== Şablonên spesîfîk ==
{{demo|sep=dide: |<nowiki>{{J:Mem û Zîn:1692}}</nowiki>}}
{{demo|sep=dide|<nowiki>{{J:Mem û Zîn:2004}}</nowiki>}}
{{demo|sep=dide: |<nowiki>{{J:Mem û Zîn:2007}}</nowiki>}}
{{demo|sep=dide: |<nowiki>{{J:Mem û Zîn:2022}}</nowiki>}}
=== Lîste ===
{{Special:PrefixIndex/Şablon:J:Mem û Zîn:|hideredirects=yes}}
==TemplateData==
<includeonly>
{{TemplateData header}}
<templatedata>
{
"params": {
"jêgirtin": {
"aliases": [
"jg"
]
},
"sernav": {},
"amadekar": {
"required": true
},
"weşanxane": {},
"sala weşandinê": {
"required": true
},
"çap": {},
"cild": {},
"rûpel": {
"aliases": [
"r"
],
"required": true
},
"isbn": {},
"url": {}
},
"description": "Ji bo jêgirtinên ji Ehmedê Xanî, Mem û Zîn. Ji kerema xwe vê şablonê bêyî tijekirina malûmatên mecbûrî li rûpelan zêde nekin!",
"format": "inline"
}
</templatedata>
[[Kategorî:Jêgirtin li gorî nivîskarên klasîk ên {{navê zimanî|ku}}|Ehmedê Xanî]]
{{quotecat|ku|sort=Ehmedê Xanî}}
</includeonly>
pr10502opuxbsri1mhneu0culqa44me
Bikarhêner:Wikihez/Gadget-test/data.json
2
1358587
6334365
6334276
2026-08-17T16:24:35Z
Wikihez
7344
6334365
json
application/json
{
"langChoices": {
"ku": "kurmancî",
"zza": "zazakî",
"ckb": "soranî",
"sdh": "kurdiya başûrî",
"hac": "hewramî",
"de": "almanî",
"ar": "erebî",
"fa": "farisî",
"fr": "fransî",
"fi": "fînî",
"en": "îngilîzî",
"ja": "japonî",
"sv": "swêdî",
"tr": "tirkî"
},
"posChoices": {
"navdêr": "Navdêr (îsm)",
"lêker": "Lêker (fiîl)",
"rengdêr": "Rengdêr (sifet)",
"hoker": "Hoker (zerf)",
"serenav": "Serenav (îsmê xisûsî)",
"artîkel": "Artîkel",
"baneşan": "Baneşan (nîda)",
"bazinedaçek": "Bazinedaçek",
"cînav": "Cînav (zemîr)",
"daçek": "Daçek (edat)",
"girêdek": "Girêdek",
"gotineke pêşiyan": "Gotineke pêşiyan",
"hejmar": "Hejmar",
"hevok": "Hevok (cimle)",
"îfade": "Îfade",
"kurtenav": "Kurtenav",
"navgir": "Navgir",
"paşdaçek": "Paşdaçek",
"paşgir": "Paşgir",
"pêşdaçek": "Pêşdaçek",
"pêşgir": "Pêşgir",
"pirtik": "Pirtik",
"reh": "Reh",
"sembol": "Sembol",
"tîp": "Tîp (herf)"
},
"posExamples": {
"navdêr": "Kelîmeyên ji bo tiştan (sêv, heywan, qedeh) yan terman (dilşadî, bextiyarî hwd.) di rêzimanê de.",
"lêker": "Kelîmeyên ji bo kirin an bûyînê wekî 'nivîsîn' yan 'xeber dan'.",
"rengdêr": "Kelîmeyên ku wesfa navdêran nîşan didin wekî 'sêva sor' yan 'xwarina şîrîn'.",
"hoker": "Kelîmeyên ku dem, cih yan awayê kirinê diyar dikin wekî 'bi vî awayî' yan 'demkî'.",
"daçek": "Kelîmeyên ku cih, dem yan hereketê diyar dikin wekî 'di.. de' yan 'ji'.",
"cînav": "Kelîmeyên ku li şûna navdêran tên bikaranîn daku navdêr neyên dubarekirin wekî 'ew' yan 'em'.",
"baneşan": "Kelîmeyên ku hest, deng hwd. îfade dikin wekî 'noşî!' yan 'aferîn'.",
"serenav": "Navdêrên ku şexsiyetên ferdî nîşan didin, wek navên kes, cih an organîzasyonan nimûne 'London' yan 'Diyarbekir'.",
"gotineke pêşiyan": "Gotinên ku di nav xelkê de bi berbelavî tên bikaranîn bo nimûne 'Serê neêşe ne hewceyî destmalê ye'."
},
"genderLabels": {
"m": "mê",
"n": "nêr",
"nt": "nêtar",
"mn": "mê yan nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"mndudil": "mê yan nêr bikaranîn dudil e",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": {
"rastnivîs": {
"langs": [
"ku"
],
"noLang": true
},
"ku-Latn": {
"langs": [
"ku"
],
"noLang": true
},
"ku-tewîna-nav": {
"langs": [
"ku"
],
"noLang": true
},
"sdh-Arab": {
"langs": [
"sdh"
],
"noLang": true
},
"hac-Arab": {
"langs": [
"hac"
],
"noLang": true
},
"ja-romanîzekirin": {
"langs": [
"ja"
],
"noLang": true
},
"ckb-Arab": {
"langs": [
"ckb"
],
"noLang": true
},
"zza-awayê-Jacobsonê": {
"langs": [
"zza"
]
},
"awayekî din": {
"langs": null
},
"guharto": {
"langs": null
},
"hevmaneya": {
"langs": null
},
"kevnbûyî": {
"langs": null
},
"kêmpeyda": {
"langs": null
},
"kurtkirin": {
"langs": null
},
"paşnav": {
"langs": null
},
"şaşnivîs": {
"langs": null
}
},
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"width": "wide"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal",
"multiline": true,
"width": "wide"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cih",
"label": "Cih"
},
{
"key": "sal",
"label": "Sal",
"width": "narrow"
},
{
"key": "çap",
"label": "Çap",
"width": "narrow"
},
{
"key": "rûpel",
"label": "Rûpel",
"width": "narrow"
},
{
"key": "isbn",
"label": "ISBN",
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"width": "wide"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "nivîskar2",
"label": "Nivîskar 2"
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"width": "wide"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal",
"multiline": true,
"width": "wide"
},
{
"key": "kovar",
"label": "Kovar"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cild",
"label": "Cild",
"width": "narrow"
},
{
"key": "hejmar",
"label": "Hejmar",
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"width": "narrow"
},
{
"key": "rûpel",
"label": "Rûpel",
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"width": "wide"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"width": "wide"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "websîte",
"label": "Websîte"
},
{
"key": "url",
"label": "URL",
"width": "wide"
},
{
"key": "arşîv",
"label": "Arşîva URLê",
"width": "wide"
},
{
"key": "sal",
"label": "Sal",
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"width": "narrow"
}
]
}
},
"pronTpls": {
"ku-IPA": {
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
"ku-kîte": {
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
"zza-IPA": {
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
"zza-kîte": {
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
"IPA": {
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
"deng": {
"desc": "Nimûneya telafûzê bi dosyeya dengî",
"snippet": "* {{deng|CODE|navê_dosyayê.ogg|dever=}}"
},
"bilêv": {
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
"kîte": {
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
"hevdeng": {
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
},
"etymTpls": {
"ji": {
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
"deyn": {
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
"mîr": {
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
"qertaf": {
"desc": "Ji bi pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
"q": {
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
"hv": {
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
"nh": {
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
"cotreh": {
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
"yekser": {
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
"wergirtin": {
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
"paşvesazî": {
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
"qusandin": {
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
"têkilkirin": {
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
"nyz": {
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
"onom": {
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
"navmiletî": {
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
"neo": {
"desc": "neolojîzm — {{neolojîzm|ku|hûr=1}}",
"snippet": "{{neolojîzm|CODE}}"
}
},
"metadata": {
"aa": {
"haswikt": 1,
"sc": [
"Latn",
"Ethi"
]
},
"ab": {
"haswikt": 1,
"sc": [
"Cyrl",
"Latn",
"Geor"
]
},
"aer": {
"sc": "Latn"
},
"af": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ak": {
"haswikt": 1
},
"akk": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Xsux"
},
"als": {
"haswikt": 1
},
"am": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1,
"sc": "Ethi"
},
"an": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ang": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"ar": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"alt": 1,
"haswikt": 1,
"sc": "Arab"
},
"arc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": [
"Hebr",
"Syrc"
]
},
"are": {},
"arz": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Arab"
},
"as": {
"haswikt": 1,
"sc": "Beng"
},
"ast": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"av": {
"haswikt": 1
},
"ay": {
"haswikt": 1
},
"az": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl",
"Arab"
]
},
"ba": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"bar": {},
"bat-smg": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"bbl": {
"sc": "Geor"
},
"be": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"be-x-old": {
"sc": "Cyrl"
},
"bg": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"bh": {
"haswikt": 1,
"sc": "Deva"
},
"bhb": {
"sc": "Deva"
},
"bi": {
"haswikt": 1
},
"blt": {
"sc": "Tavt"
},
"bm": {
"haswikt": 1,
"sc": [
"Latn",
"Nkoo",
"Arab"
]
},
"bn": {
"haswikt": 1,
"sc": "Beng"
},
"bo": {
"haswikt": 1,
"sc": "Tibt"
},
"br": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ca": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"cdo": {
"sc": "Hani"
},
"ce": {
"sc": "Cyrl"
},
"ch": {
"haswikt": 1
},
"chr": {
"haswikt": 1,
"sc": "Cher"
},
"cjy": {
"sc": "Hani"
},
"ckb": {
"haswikt": 1,
"sc": "Arab",
"wsc": "ku-Arab"
},
"cmn": {
"sc": "Hani",
"wiktprefix": "zh"
},
"co": {
"haswikt": 1
},
"cpx": {
"sc": "Hani"
},
"cr": {
"haswikt": 1,
"sc": "Cans"
},
"crh": {},
"cs": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"csb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"cu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": [
"Cyrs",
"Glag"
]
},
"cv": {
"sc": "Cyrl"
},
"cy": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"czh": {
"sc": "Hani"
},
"czo": {
"sc": "Hani"
},
"da": {
"g": [
"g",
"nt",
"g-p",
"nt-p",
"p"
],
"haswikt": 1
},
"dax": {},
"de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"dhg": {},
"djb": {},
"dji": {},
"djr": {},
"dng": {
"sc": "Cyrl"
},
"dsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"dsx": {},
"duj": {},
"dv": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Thaa"
},
"dz": {
"haswikt": 1,
"sc": "Tibt"
},
"el": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"en": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"eo": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"es": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"et": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ett": {
"g": [
"y",
"p"
],
"sc": "Ital"
},
"eu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fa": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-cls": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-ira": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fi": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fil": {},
"fj": {
"haswikt": 1
},
"fo": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1
},
"fr": {
"g": [
"n",
"m",
"mnmane",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"frm": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fro": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fur": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fy": {
"g": [
"g",
"nt",
"p"
],
"haswikt": 1
},
"ga": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"gan": {
"sc": "Hani"
},
"gd": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gez": {
"sc": "Ethi"
},
"gl": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gmw-ecg": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gmy": {
"sc": "Linb"
},
"gn": {
"haswikt": 1
},
"gnn": {
"sc": "Latn"
},
"got": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"sc": "Goth"
},
"grc": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
]
},
"gsw": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Gujr"
},
"guf": {
"sc": "Latn"
},
"gv": {
"haswikt": 1
},
"ha": {
"haswikt": 1
},
"hak": {
"sc": "Hani"
},
"har": {
"sc": "Ethi"
},
"he": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"hi": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Deva"
},
"hif": {
"sc": [
"Latn",
"Deva"
]
},
"hit": {
"sc": "Xsux"
},
"hrx": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"hsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"hsn": {
"sc": "Hani"
},
"hu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"hy": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Armn"
},
"ia": {
"haswikt": 1
},
"id": {
"haswikt": 1
},
"ie": {
"haswikt": 1
},
"ik": {
"haswikt": 1
},
"ike": {
"sc": "Cans"
},
"ikt": {
"sc": "Cans"
},
"io": {
"haswikt": 1
},
"is": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"it": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"iu": {
"haswikt": 1,
"sc": "Cans"
},
"ja": {
"haswikt": 1,
"sc": "Jpan"
},
"jay": {
"sc": "Latn"
},
"jbo": {
"haswikt": 1
},
"jv": {
"haswikt": 1
},
"ka": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"khb": {
"sc": [
"Talu",
"Lana",
"Thai"
]
},
"kjh": {
"sc": "Cyrl"
},
"kk": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"kkh": {
"sc": [
"Lana",
"Thai"
]
},
"kl": {
"haswikt": 1
},
"km": {
"haswikt": 1,
"sc": "Khmr"
},
"kn": {
"haswikt": 1,
"sc": "Knda"
},
"ku": {
"g": [
"n",
"m",
"mnmane",
"mndudil",
"n-p",
"m-p",
"p"
],
"wiktprefix": "ku"
},
"ko": {
"haswikt": 1,
"sc": "Kore"
},
"krc": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"ks": {
"haswikt": 1,
"sc": [
"Arab",
"Deva"
],
"wsc": "ks-Arab"
},
"kw": {
"haswikt": 1
},
"ky": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"la": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"lb": {
"g": [
"n",
"m",
"nt",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"lez": {
"sc": "Cyrl"
},
"li": {
"haswikt": 1
},
"lki": {
"wiktprefix": "ku"
},
"lld": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lmo": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ln": {
"haswikt": 1
},
"lo": {
"haswikt": 1,
"sc": "Laoo"
},
"lt": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lv": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lzz": {
"sc": "Geor"
},
"mg": {
"haswikt": 1
},
"mh": {
"haswikt": 1
},
"mi": {
"g": 0,
"haswikt": 1
},
"mk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ml": {
"haswikt": 1,
"sc": "Mlym"
},
"mn": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Cyrl",
"Mong"
]
},
"mnp": {
"sc": "Hani"
},
"mo": {
"haswikt": 1,
"sc": "Cyrl"
},
"mol": {
"sc": "Cyrl"
},
"mr": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1,
"sc": "Deva"
},
"ms": {
"haswikt": 1,
"sc": [
"Latn",
"Arab"
]
},
"mt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"mwp": {
"sc": "Latn"
},
"my": {
"haswikt": 1,
"sc": "Mymr"
},
"na": {
"haswikt": 1
},
"nah": {
"haswikt": 1
},
"nan": {
"wiktprefix": "zh-min-nan",
"sc": "Hani"
},
"nb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"wiktprefix": "no"
},
"nds-de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"nds-nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"ne": {
"haswikt": 1,
"sc": "Deva"
},
"nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nn": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"no": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nod": {
"sc": "Lana"
},
"non": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"oc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"om": {
"haswikt": 1
},
"or": {
"haswikt": 1,
"sc": "Orya"
},
"orv": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrs"
},
"osc": {
"sc": "Ital"
},
"ota": {
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "ota-Arab"
},
"pa": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": [
"Guru",
"Arab"
]
},
"pdt": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"peo": {
"sc": "Xpeo"
},
"phn": {
"sc": "Phnx"
},
"pi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"pjt": {
"sc": "Latn"
},
"pl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"pms": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"pox": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"împf",
"pf"
]
},
"prs": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"ps": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ps-Arab"
},
"pt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"qu": {
"haswikt": 1
},
"rgn": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"rit": {
"sc": "Latn"
},
"rm": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"rn": {
"haswikt": 1
},
"ro": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"roa-rup": {
"haswikt": 1
},
"ru": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"rue": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrl"
},
"ruo": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rup": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "roa-rup"
},
"ruq": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rw": {
"haswikt": 1
},
"sa": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Deva"
},
"sah": {
"sc": "Cyrl"
},
"sc": {
"haswikt": 1
},
"scn": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"sco": {
"sc": "Latn"
},
"sd": {
"haswikt": 1,
"sc": "Arab",
"wsc": "sd-Arab"
},
"sdh": {
"sc": "Arab",
"wiktprefix": "ku"
},
"sg": {
"haswikt": 1
},
"sh": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"si": {
"haswikt": 1,
"sc": "Sinh"
},
"simple": {
"haswikt": 1
},
"sk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sl": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sm": {
"haswikt": 1
},
"sn": {
"haswikt": 1
},
"so": {
"haswikt": 1
},
"spx": {
"sc": "Ital"
},
"sq": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ss": {
"haswikt": 1
},
"st": {
"haswikt": 1
},
"su": {
"haswikt": 1
},
"sux": {
"sc": "Xsux"
},
"sv": {
"g": [
"g",
"nt",
"g-p",
"nt-p"
],
"haswikt": 1
},
"sva": {
"sc": "Geor"
},
"sw": {
"nclass": 1,
"haswikt": 1
},
"syc": {
"sc": "Syrc"
},
"syr": {
"sc": "Syrc"
},
"szl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"ta": {
"haswikt": 1,
"sc": "Taml"
},
"tdd": {
"sc": "Tale"
},
"te": {
"haswikt": 1,
"sc": "Telu"
},
"tg": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Cyrl"
},
"th": {
"haswikt": 1,
"sc": "Thai"
},
"ti": {
"haswikt": 1,
"sc": "Ethi"
},
"tig": {
"sc": "Ethi"
},
"tiw": {
"sc": "Latn"
},
"tk": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tl": {
"haswikt": 1,
"sc": [
"Latn",
"Tglg"
]
},
"tmr": {
"sc": "Hebr"
},
"tn": {
"haswikt": 1
},
"to": {
"haswikt": 1
},
"tpi": {
"haswikt": 1
},
"tr": {
"g": [
"y",
"p"
],
"alt": 1,
"haswikt": 1
},
"ts": {
"haswikt": 1
},
"tt": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tts": {
"sc": "Thai"
},
"tw": {
"haswikt": 1
},
"udi": {
"sc": [
"Cyrl",
"Latn",
"Armn",
"Geor"
]
},
"ug": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ug-Arab"
},
"uga": {
"sc": "Ugar"
},
"uk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ulk": {
"sc": "Latn"
},
"ur": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ur-Arab"
},
"uz": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"vec": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"vi": {
"haswikt": 1
},
"vls": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"vo": {
"haswikt": 1
},
"wa": {
"haswikt": 1
},
"wbp": {
"sc": "Latn"
},
"wo": {
"haswikt": 1
},
"wuu": {
"sc": "Hani"
},
"xae": {
"sc": "Ital"
},
"xcr": {
"sc": "Cari"
},
"xfa": {
"sc": "Ital"
},
"xh": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"xlc": {
"sc": "Lyci"
},
"xld": {
"sc": "Lydi"
},
"xlu": {
"sc": "Xsux"
},
"xmf": {
"sc": "Geor"
},
"xno": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"xrr": {
"sc": "Ital"
},
"xst": {
"sc": "Ethi"
},
"xum": {
"sc": "Ital"
},
"xve": {
"sc": "Ital"
},
"xvo": {
"sc": "Ital"
},
"yi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"yo": {
"haswikt": 1
},
"yua": {
"g": [
"y",
"p"
],
"alt": 1
},
"yue": {
"sc": "Hani"
},
"za": {
"haswikt": 1,
"sc": "Latn"
},
"zh-classical": {
"sc": "Hani"
},
"zh-min-nan": {
"haswikt": 1
},
"zh-yue": {
"sc": "Hani"
},
"zu": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"zza": {
"g": [
"n",
"m",
"mn",
"n-p",
"m-p",
"p"
],
"wiktprefix": "diq"
}
},
"ziman": {
"ku": "kurmancî",
"en": "îngilîzî",
"tr": "tirkî",
"de": "almanî",
"pl": "polonî",
"ja": "japonî",
"fa": "farisî",
"sh": "sirboxirwatî",
"fr": "fransî",
"ka": "gurcî",
"it": "îtalî",
"fi": "fînî",
"ru": "rusî",
"es": "spanî",
"ar": "erebî",
"nl": "holendî",
"hi": "hindî",
"cs": "çekî",
"cmn": "mandarînî",
"pt": "portugalî",
"uk": "ûkraynî",
"hu": "mecarî",
"ko": "koreyî",
"be": "belarusî",
"la": "latînî",
"ckb": "soranî",
"sv": "swêdî",
"hy": "ermenî",
"bg": "bulgarî",
"ro": "romanyayî",
"da": "danmarkî",
"gl": "galîsî",
"el": "yûnanî",
"eo": "esperantoyî",
"tl": "tagalogî",
"fo": "ferî",
"vi": "viyetnamî",
"ca": "katalanî",
"is": "îzlendî",
"et": "estonî",
"az": "azerî",
"ceb": "sebwanoyî",
"mk": "makedonî",
"he": "îbranî",
"th": "tayî",
"lv": "latviyayî",
"sk": "slovakî",
"ms": "malezî",
"ga": "îrlendî",
"tg": "tacikî",
"grc": "yûnaniya kevn",
"sl": "slovenî",
"kk": "qazaxî",
"mi": "maorî",
"mn": "mongolî",
"sq": "albanî",
"uz": "ozbekî",
"lt": "lîtwanî",
"ur": "urdûyî",
"km": "ximêrî",
"id": "endonezyayî",
"cy": "weylsî",
"lo": "lawsî",
"bn": "bengalî",
"zza": "zazakî",
"ky": "kirgizî",
"yi": "yidîşî",
"gd": "gaelîka skotî",
"my": "burmayî",
"te": "telûgûyî",
"nb": "norweciya bokmålî",
"zh": "çînî",
"ps": "peştûyî",
"eu": "baskî",
"oc": "oksîtanî",
"sw": "swahîlî",
"ast": "astûrî",
"af": "afrîkansî",
"tk": "tirkmenî",
"nn": "norweciya nînorskî",
"yue": "kantonî",
"ug": "oygurî",
"rm": "romancî",
"sc": "sardînî",
"vo": "volapûkî",
"io": "îdoyî",
"ml": "malayalamî",
"mt": "maltayî",
"ba": "başkîrî",
"ta": "tamîlî",
"ang": "îngilîziya kevn",
"sa": "sanskrîtî",
"tt": "teterî",
"rup": "aromanî",
"nrf": "normandî",
"no": "norwecî",
"scn": "sicîlî",
"br": "bretonî",
"as": "asamî",
"bo": "tîbetî",
"nan": "minnanî",
"nv": "navajoyî",
"fy": "frîsî",
"pa": "puncabî",
"vec": "venîsî",
"fur": "friyolî",
"wa": "walonî",
"haw": "hawayî",
"lb": "luksembûrgî",
"am": "amharî",
"arz": "erebiya misrî",
"ia": "înterlîngua",
"kl": "kalalîsûtî",
"mr": "maratî",
"gv": "manksî",
"qu": "keçwayî",
"dsb": "sorbiya jêrîn",
"si": "kîngalî",
"kn": "kannadayî",
"sdh": "kurdiya başûrî",
"ota": "osmanî",
"gu": "gujaratî",
"ce": "çeçenî",
"jv": "javayî",
"pdt": "plodîşî",
"chr": "çerokî",
"nds": "nedersaksî",
"acw": "erebiya hîcazî",
"cu": "slaviya kevn",
"ne": "nepalî",
"kw": "kornî",
"hsb": "sorbiya jorîn",
"za": "zhuangî",
"os": "osetî",
"se": "samiya bakurî",
"dng": "dunganî",
"izh": "îngriyî",
"sco": "skotî",
"got": "gotî",
"or": "oriyayî",
"mnc": "mançûyî",
"crh": "teteriya krîmî",
"ltg": "latgalî",
"sah": "yakutî",
"ht": "haîtî",
"ary": "erebiya fasî",
"ha": "hawsayî",
"zu": "zuluyî",
"cv": "çuvaşî",
"yo": "yorubayî",
"ab": "abxazî",
"so": "somalî",
"frr": "frîsiya bakur",
"lad": "ladînoyî",
"an": "aragonî",
"ti": "tigrînî",
"aii": "aramiya nû ya asûrî",
"hak": "hakkayî",
"enm": "îngilîziya navîn",
"pi": "palî",
"navz": "navzimanî",
"ny": "çîçewayî",
"om": "oromoyî",
"nds-de": "almaniya jêrîn a almanî",
"lmo": "lombardî",
"csb": "kaşûbî",
"alt": "altayiya başûrî",
"arc": "aramî",
"bcl": "bîkoliya naverast",
"co": "korsîkayî",
"oj": "ojibweyî",
"pap": "papyamentoyî",
"rue": "rusînî",
"av": "avarî",
"txb": "toxarî B",
"xal": "kalmîkî",
"cop": "qiptî",
"mg": "malagasî",
"nap": "napolîtanî",
"gsw": "elmanîşî",
"dlm": "dalmatî",
"gn": "guwaranî",
"orv": "slaviya rojavayî ya kevn",
"tyv": "tuvanî",
"dv": "divehî",
"ady": "adîgeyî",
"bua": "buryatî",
"wuu": "wuyî",
"pam": "kapampanganî",
"myv": "erzayî",
"udm": "udmurtî",
"khb": "lueyî",
"rom": "romanî",
"su": "sundanî",
"ilo": "îlokanoyî",
"syc": "siryaniya klasîk",
"zdj": "şîngazîcayî",
"cdo": "min dongî",
"evn": "evenkî",
"kum": "kumikî",
"bal": "belûçî",
"lld": "ladînî",
"inh": "îngûşî",
"sd": "sindhî",
"lez": "lezgînî",
"fro": "fransiya kevn",
"non": "norsiya kevn",
"liv": "lîvonî",
"tet": "tetûmî",
"tpi": "tokpisinî",
"shn": "şanî",
"wo": "wolofî",
"mrw": "maranaoyî",
"gld": "nanayî",
"nah": "nahwatlî",
"ki": "kîkûyûyî",
"st": "sotoyiya başûr",
"mww": "hmongî",
"lij": "lîgûrî",
"rgn": "romagnolî",
"li": "lîmbûrgî",
"sm": "samoayî",
"sga": "îrlendiya kevn",
"ks": "keşmîrî",
"pms": "piedmontîsî",
"ccc": "çamicuroyî",
"hil": "hîlîgaynonî",
"xmf": "megrelî",
"hrx": "hunsrikî",
"rif": "tarifitî",
"krc": "karaçay-balkarî",
"war": "warayî",
"mwl": "mîrandî",
"syl": "sîlhetî",
"ee": "eweyî",
"ln": "lingalayî",
"pdc": "almaniya Pennsylvania",
"goh": "almaniya bilind a kevn",
"tly": "talişî",
"nci": "nahwatliya klasîk",
"ckt": "çukçî",
"ain": "aynuyî",
"uga": "ugarîtî",
"xh": "xosayî",
"sms": "samiya skoltî",
"kea": "kapverdî",
"ajp": "erebiya başûrê şamê",
"kjh": "xakasî",
"iu": "înuîtî",
"dz": "conxayî",
"srn": "sirananî",
"vro": "voroyî",
"mdf": "mokşayî",
"xcl": "ermeniya kevn",
"mhr": "mariya rojhilatî",
"cic": "çîkasawî",
"esu": "yupîkiya navendî",
"cjs": "şorî",
"wym": "wîlamowî",
"apw": "apaçiya rojava",
"mba": "higaononî",
"kaw": "javayiya kevn",
"ovd": "elfdalî",
"to": "tongî",
"ist": "îstriyotî",
"szl": "silesî",
"yua": "mayayiya yukatekî",
"tsg": "tausugî",
"zea": "zêlandî",
"bar": "bayerî",
"luy": "luhyayî",
"mzn": "mazenderanî",
"mhn": "mòçenoyî",
"kxd": "bruneyî",
"lzz": "lazî",
"kbd": "kabardî",
"afb": "erebiya kendavî",
"krl": "karelî",
"eve": "evenî",
"stq": "frîsiya saterlandî",
"ak": "akanî",
"tab": "tabasaranî",
"sat": "santalî",
"fj": "fîjî",
"sva": "svanî",
"egy": "misrî",
"ig": "îgboyî",
"hac": "hewramî",
"ay": "aymarayî",
"gur": "farefareyî",
"rw": "kînyarwandayî",
"twf": "taosî",
"koi": "komî-permyakî",
"lkt": "lakotayî",
"akl": "aklanon",
"yai": "yagnobî",
"ryu": "okînawanî",
"rhg": "rohingyayî",
"vep": "vepsî",
"smn": "samiya înarî",
"egl": "emilî",
"ty": "tahîtî",
"mdh": "maguindanaoyî",
"gag": "gagawzî",
"tzm": "temazîxtiya atlasa navendî",
"sgs": "samogîtî",
"ff": "fulahî",
"lou": "kreyoliya fransî ya Louisiana",
"mel": "melanauyiya navendî",
"niv": "nîvxî",
"aa": "afarî",
"gez": "etiyopiya klasîk",
"ext": "extremaduranî",
"frp": "frankoprovansî",
"kpv": "komî-zîriyî",
"udi": "udî",
"sma": "samiya başûr",
"nog": "nogayî",
"akk": "akadî",
"lbe": "lakî",
"min": "mînangkabawî",
"kg": "kîkongoyî",
"ban": "balînî",
"dlg": "dolganî",
"ace": "açehî",
"pox": "polabî",
"ppl": "pîpîlî",
"mnz": "monî",
"kaa": "karakalpakî",
"kab": "kabîlî",
"vot": "votî",
"luo": "luoyî",
"sn": "şonayî",
"swb": "komorî",
"roa-tar": "tarantinoyî",
"lg": "lugandayî",
"nds-nl": "saksoniya jêrîn ya holendî",
"dtp": "dusuniya navendî",
"shi": "taşelhitî",
"msk": "mansakayî",
"rap": "rapanuyî",
"mer": "meruyî",
"ik": "înupîakî",
"prg": "prusiya kevn",
"chk": "çûkî",
"kam": "kambayî",
"chy": "çeyenî",
"nod": "tayiya bakurî",
"akz": "alabamayî",
"xag": "aghwanî",
"ase": "zimanê destan ê emerîkî",
"nmn": "ǃxóõ",
"gun": "mbya guaraniyî",
"cr": "krî",
"yag": "yamanayî",
"otk": "tirkiya kevn",
"sid": "sidamoyî",
"tum": "tumbukayî",
"pjt": "pîttcantcatcarayî",
"sux": "sûmerî",
"bbl": "batsbî",
"jje": "jejuyî",
"bm": "bambarayî",
"frm": "fransiya navîn",
"tpn": "tupînambayî",
"pal": "farisiya navîn",
"bi": "bîslamayî",
"ksw": "s'gaw karenî",
"ssy": "sahoyî",
"bdr": "bajwayiya kendava rojava",
"sne": "bau bidayuh",
"sjd": "samiya kildînî",
"na": "nawrûyî",
"pag": "pangasînanî",
"abq": "abazayî",
"tn": "tswanayî",
"osx": "saksoniya kevn",
"aqc": "arçî",
"xsr": "şerpayî",
"kok": "konkanî",
"kln": "kalencînî",
"win": "wînnebagoyî",
"dar": "dargwayî",
"mfe": "kreyoliya morîtanî",
"iba": "îbanî",
"ae": "avestayî",
"mos": "moreyî",
"arn": "mapuçî",
"ill": "îranunî",
"mrj": "mariya rojavayî",
"cst": "ohloniya bakur",
"blt": "tay dam",
"apc": "erebiya bakûrê şamê",
"gan": "ganî",
"ii": "yiyiya siçuwayî",
"xto": "toxarî A",
"wmw": "mwanî",
"avd": "elwîrî-wîderî",
"pro": "oksîtaniya kevn",
"pcc": "buyî",
"jra": "jarayî",
"new": "newarî",
"unm": "unamî",
"mic": "mîkmakî",
"cjm": "çamiya rojhilatî",
"sdn": "galûrîsî",
"lus": "mizoyî",
"cjy": "jinî",
"tli": "tlingitî",
"bug": "bugî",
"pcd": "pîkardî",
"hop": "hopîyî",
"ofs": "frîsiya kevn",
"tft": "ternateyî",
"roa-opt": "portugaliya kevn",
"gil": "kîrîbatî",
"kac": "cingphoyî",
"csm": "sierra mîwokiya navendî",
"sdc": "sassarîsî",
"ood": "o'odhamî",
"hsn": "xiangî",
"ruo": "îstro-romanyayî",
"mbb": "bukidnon manoboyiya rojavayî",
"bho": "bojpûrî",
"ani": "andî",
"kgp": "kayingangî",
"ss": "swazî",
"mnp": "min beî",
"aho": "ahomî",
"moe": "montagnaîsî",
"naq": "namayî",
"mvi": "miyakoyî",
"duu": "drungî",
"lut": "luşûtsîdî",
"sce": "dongşîangî",
"bqi": "bextiyarî",
"mrc": "marîkopayî",
"gmh": "almaniya bilind a navîn",
"cim": "cimbrî",
"aer": "arrernteyiya rojavayî",
"agj": "argobbayî",
"ium": "îu mienî",
"tay": "atayalî",
"ale": "alêwîtî",
"str": "saanîçî",
"zku": "kaurnayî",
"pnt": "yûnaniya pontî",
"gmw-cfr": "frankoniya navendî",
"bxd": "pelayî",
"ems": "alutîqî",
"mtq": "muongî",
"hit": "hîtîtî",
"mak": "makasarî",
"bjn": "banjarî",
"nay": "ngarrindjerî",
"dak": "dakotayî",
"yrk": "nenetsiya tundra",
"pac": "pacohî",
"ch": "çamoroyî",
"vls": "flamiya rojava",
"acm": "erebiya iraqî",
"smj": "samiya lule",
"lmy": "lamboyayî",
"jam": "kreyoliya jameîkî",
"yka": "yakanî",
"gmw-ecg": "almaniya navendî ya rojhilat",
"xsv": "sûdovî",
"cdm": "çepangî",
"bdk": "buduxî",
"isd": "îsnagî",
"zgh": "amazîxiya fasî",
"css": "ohloniya başûr",
"kqr": "kîmaragangî",
"glk": "gîlekî",
"aeb": "erebiya tunusî",
"lki": "lekî",
"kdr": "karaîmî",
"kld": "gamîlaraayî",
"tdd": "tayî nüa",
"xqa": "karaxanîdî",
"nso": "sotoyiya bakur",
"crs": "kreyoliya seyşelî",
"mvv": "tagal murutî",
"kim": "tofalarî",
"swg": "swabî",
"pau": "palawî",
"wrh": "wiradhurî",
"cho": "çoktawî",
"tig": "tigreyî",
"ivv": "îvatanî",
"ddg": "fatalukuyî",
"rmf": "romaniya kalo fînî",
"pih": "pîtkarnî-norfolkî",
"phn": "fenîkî",
"ktu": "kitubayî",
"mwr": "marwarî",
"wbp": "warlpirî",
"zun": "zuniyî",
"crk": "kriya bejayî",
"ebk": "bontociya rojhilatî",
"nup": "nupeyî",
"yoi": "yonagunî",
"atj": "atîkamekwî",
"duo": "dupaningan agtayî",
"kca": "xantî",
"dta": "daurî",
"rar": "rarotongî",
"abe": "abenakî",
"aot": "atong (Hindistan)",
"osa": "osageyî",
"ami": "amîs",
"sea": "semaî",
"mh": "marşalî",
"mad": "madurayî",
"sei": "sêriyî",
"moh": "mohawkî",
"meu": "hîrî motuyî",
"pwn": "paîwanî",
"okm": "koreyiya navîn",
"xug": "kunigamî",
"amu": "amuzgoyî",
"ine-pro": "proto-hindûewropî",
"tzo": "tzotzîlî",
"ksc": "kalingayiya başûrî",
"sib": "sebopî",
"uum": "ûrûmî",
"kjj": "khinalugî",
"pis": "pijînî",
"peo": "farisiya kevn",
"sg": "sangoyî",
"tyz": "tàyî",
"kpy": "koryakî",
"ruq": "megleno-romanyayî",
"abs": "ambonezî-malezî",
"mas": "masayî",
"bdq": "bahnarî",
"mkz": "makasaeyî",
"mag": "magahî",
"oaa": "orokî",
"din": "dinkayî",
"zlw-slv": "slovînsî",
"olo": "livvî",
"mai": "maithilî",
"nhn": "nahwatliya navendî",
"nan-tws": "teoçewî",
"wuh": "wutunhuayî",
"tpw": "tûpiya kevn",
"gcf": "kreyoliya antîlleyî",
"knb": "lubuagan kalingayî",
"ket": "ketî",
"pua": "purepeçayî",
"xbr": "kamberayî",
"bor": "bororoyî",
"rmy": "romaniya vlaks",
"tao": "yamî",
"arq": "erebiya cezayirî",
"brh": "brahuiyî",
"chn": "çinook jargonî",
"cbk": "çavacanoyî",
"tvo": "tidoreyî",
"wbk": "waigalî",
"nio": "nganasanî",
"bsh": "kamkata-viriyî",
"roa-gal": "galoyî",
"krj": "kinaray-ayî",
"gmy": "yûnaniya mîkenî",
"ctd": "tedim çinî",
"ude": "udiheyî",
"gmw-rfr": "frankoniya rhine",
"tvl": "tuvalûyî",
"ibg": "îbanagî",
"ask": "aşkunî",
"ccp": "çakmayî",
"ts": "tsongayî",
"sje": "pite samî",
"esh": "iştihardî",
"quc": "k'içe'",
"ho": "hirî motuyî",
"hif": "fiji hindiyî",
"orh": "oroqenî",
"ett": "etrûskî",
"zai": "îsthmus zapotekî",
"bla": "blakfotî",
"zlw-opl": "poloniya kevn",
"ses": "sonxayî",
"lis": "lisuyî",
"kr": "kanuriyî",
"bbc": "bataktobayî",
"umb": "umbunduyî",
"rys": "yaeyamayî",
"arp": "arapaho",
"bol": "boleyî",
"com": "komançî",
"ifk": "tuwali ifugaoyî",
"ve": "vendayî",
"kri": "krioyî",
"aoz": "uab meto",
"yxg": "yagarayî",
"zom": "zouyî",
"ttt": "tatî",
"ams": "amamî-oşîmayiya başûrî",
"tts": "isanî",
"igl": "îgalayî",
"xda": "darkinjungî",
"fkv": "kvenî",
"chg": "çaxatayî",
"xwo": "oyratiya nivîsî",
"cab": "garîfunayî",
"atv": "altayiya bakurî",
"tiw": "tiwî",
"fit": "meänkiyelî",
"mnk": "mandinkayî",
"yox": "yoronî",
"tfn": "dena'inayî",
"hur": "halkomelemî",
"ryn": "amamî-oşimayiya bakurî",
"rop": "kriolî",
"ckv": "kavalanî",
"ulc": "ulçî",
"hts": "hadzayî",
"anq": "jarawa",
"dgr": "dogrîbî",
"myp": "pîrahayî",
"xfa": "faliskanî",
"gin": "hinukhî",
"pqm": "malecite-passamaquoddyî",
"mni": "manipurî",
"txg": "tangutî",
"trv": "tarokoyî",
"kha": "khasiyî",
"odt": "holendiya kevn",
"tby": "tabaruyî",
"drg": "rungusî",
"yur": "yurokî",
"neg": "negidalî",
"mga": "îrlendiya navîn",
"gor": "gorontaloyî",
"tcy": "tuluyî",
"oac": "oroçî",
"nia": "nîasî",
"skb": "saekî",
"bth": "biatah bidayuhî",
"nwy": "nottoway-meherrînî",
"chc": "katawbayî",
"bgc": "haryanvî",
"tih": "timugon murutî",
"itl": "îtelmenî",
"gml": "almaniya jêrîn a navîn",
"apj": "xîkarîyayî",
"yao": "yaoyî",
"okn": "oki-no-erabu",
"juc": "jurçenî",
"bnn": "bununî",
"nmb": "nambasiya mezin",
"cuk": "kunayî",
"uby": "ûbîkî",
"bpy": "manîpûriya bîşnûpriyayî",
"alr": "alutor",
"nys": "nyungayî",
"niq": "nandî",
"nnt": "nanticokeyî",
"shy": "şawî",
"xum": "umbriyî",
"kzg": "kikaiyî",
"blk": "pa'o karen",
"khi-kun": "ǃkungî",
"amw": "neo-aramiya rojavayî",
"kyh": "karokî",
"arw": "arawak",
"rkt": "kamtayî",
"ijc": "îzonî",
"jct": "krymçakî",
"xpq": "mohegan-pequotî",
"lnd": "lundayehî",
"xnb": "kanakanabuyî",
"niu": "nîwî",
"wls": "walîsî",
"axm": "ermeniya navîn",
"kap": "bezhtayî",
"ykg": "yukaghiriya bakurî",
"kky": "guugu yimidhirrî",
"hnj": "hmongiya hêşîn",
"dtr": "lotudî",
"kla": "klamatî-modokî",
"fax": "falayî",
"sjt": "ter samî",
"nxq": "naxî",
"oge": "gurciya kevn",
"apm": "çîrîkawayî",
"tzj": "tzutucîlî",
"tbl": "tbolî",
"inc-oas": "asamiya zû",
"hai": "haidayî",
"sou": "taiya başûrî",
"osc": "oskanî",
"shh": "şoşonî",
"xmz": "morî bawah",
"sju": "ume samî",
"xpr": "partî",
"gaa": "gayî",
"nch": "nahwatliya huastecaya navendî",
"bsk": "buruşaskiyî",
"bzj": "kreyoliya belizeyî",
"nhx": "nahwatliya mekayapan",
"ktb": "kambaatayî",
"xnn": "kankanayiya bakurî",
"pot": "potawatomî",
"agr": "aguarunayî",
"bsb": "bisayayiya bruneyî",
"cja": "çamiya rojavayî",
"nov": "noviyalî",
"kbp": "kabiyeyî",
"tkn": "toku-no-şîmayî",
"lzh": "çîniya klasîk",
"caa": "ç'orti'",
"paw": "pawnî",
"wbl": "waxiyî",
"rut": "rutulî",
"lud": "ludianî",
"gon": "gondiyî",
"lsd": "lîşana denî",
"pyu": "puyumayî",
"bej": "bejayî",
"sxr": "saaroayî",
"bsq": "bassayî",
"cmg": "mongoliya klasîk",
"mus": "krîkî",
"sas": "sasakî",
"mlm": "mulamî",
"aio": "aytonî",
"nuk": "nootkayî",
"aiw": "aarî",
"enq": "engayî",
"chl": "cahuillayî",
"huz": "hunzibî",
"grt": "garoyî",
"lic": "hlaiyî",
"fia": "nobiinî",
"osp": "spaniya kevn",
"amm": "amayî",
"ote": "otomiya mezquital",
"roa-leo": "leonîzî",
"dum": "holendiya navîn",
"awa": "awadhî",
"otw": "ottawayî",
"xbc": "baxterî",
"bem": "bembayî",
"ira-pro": "proto-îranî",
"dbl": "diyirbalî",
"mcm": "kristangî",
"ctu": "çolî",
"grk-mar": "yûnaniya mariupol",
"nst": "tangsayî",
"cak": "kaqçikelî",
"pov": "kreyoliya guinea-bissau",
"mbf": "malayiya baba",
"xsy": "saisiyatî",
"bno": "asî",
"lbj": "ladakhiyî",
"adt": "adnyamatanhayî",
"ttj": "tooroyî",
"apy": "apalayî",
"pim": "powhatanî",
"roa-brg": "bûrgûndî",
"szy": "sakizayayî",
"tkl": "tokelawî",
"wlm": "weylsiya navîn",
"gdo": "godoberî",
"ayn": "erebiya sanaanî",
"ksk": "kansayî",
"aht": "ahtnayî",
"btx": "batakiya karo",
"jdt": "tatiya cihû",
"cni": "aşanînkayî",
"skr": "seraikî",
"iir-pro": "proto-hindûîranî",
"yrl": "nhêngatûyî",
"sip": "sîkkîmî",
"ie": "înterlîngue",
"bin": "edoyî",
"sac": "foksî",
"wni": "komoriya ndzwanî",
"btq": "batekî",
"bkd": "binukid",
"gmq-scy": "skanî",
"rah": "rabhayî",
"ddo": "tsezî",
"tuw-sol": "solonî",
"gni": "gooniyandiyî",
"rmn": "romaniya balkanî",
"roa-poi": "poitevin-saintongeaisî",
"mqm": "marquesana başûr",
"tsu": "tsouyî",
"yol": "yolayî",
"fud": "futunî",
"umu": "munsî",
"wam": "massaçusettî",
"bsy": "sabah bisayayî",
"prn": "prasunî",
"clm": "klallamî",
"ssf": "thaoyî",
"jbo": "lojbanî",
"nus": "nuerî",
"mhx": "lhao voyî",
"hmc": "huişui hmongiya navendî",
"lrc": "luriya bakurî",
"tiy": "tirurayî",
"kkh": "khünî",
"nfr": "nafaanrayî",
"zpq": "zapotekiya zoogoço",
"suk": "sukumayî",
"rml": "romaniya baltîkî",
"ncj": "nahwatliya bakurê puebla",
"nhg": "nahwatliya tetelcingo",
"ars": "erebiya necdî",
"bh": "biharî",
"caq": "car nicobareseyî",
"wlc": "mwalî komorî",
"apd": "erebiya sudanî",
"mop": "mayayiya mopanî",
"yaq": "yaquî",
"pon": "ponpeyî",
"kwk": "kwak'walayî",
"wlo": "wolioyî",
"nnr": "narunggayî",
"agx": "aghulî",
"dty": "doteliyî",
"ybh": "yakkhayî",
"azz": "puebla nahuatliya çiyayî",
"yuy": "yuguriya rojhilat",
"tqw": "tonkawayî",
"hss": "harsusiyî",
"mqu": "mandarî",
"maz": "mazahuayiya navendî",
"ife": "îfèî",
"kmk": "limos kalingayî",
"shs": "şuswapî",
"hid": "hidatsayî",
"brg": "baureyî",
"wrr": "wardamanî",
"kis": "kîsî",
"acy": "erebiya qibrisî",
"tin": "tindî",
"gmq-osw": "swêdiya kevn",
"yuf": "havasupaî-walapaî-yavapaî",
"xdk": "darûkî",
"wau": "waujayî",
"ljp": "lampungî",
"alu": "areareyî",
"ach": "açolî",
"bkm": "komiya kamerûnî",
"ngn": "ngwoyî",
"bsa": "abînomnî",
"der": "deoriyî",
"shp": "şipibo-coniboyî",
"ycl": "lolopoyî",
"qua": "kuapawî",
"gmq-pro": "proto-norsî",
"nnv": "nukunuyî",
"zko": "kottî",
"khv": "khvarşiyî",
"nij": "ngajuyî",
"yog": "yogadî",
"raw": "rawangî",
"mfy": "mayoyî",
"nd": "ndebeliya bakurî",
"oko": "koreyiya kevn",
"fon": "fonî",
"abx": "abaknonî",
"rad": "radeyî",
"klj": "khalajî",
"tru": "turoyo",
"nag": "naga pidginî",
"crl": "kriya rojhilat ya bakurî",
"zlw-ocs": "çekiya kevn",
"aau": "abauyî",
"mrq": "marquesana bakur",
"sia": "samiya akkalayî",
"mjg-mgl": "mongghulî",
"car": "galîbî karîbî",
"roa-tou": "torangî",
"rel": "rendilleyî",
"oma": "omaha-poncayî",
"rmq": "kaloyî",
"rmo": "romaniya sinte",
"lep": "lepçayî",
"guw": "gunî",
"knj": "akatekî",
"slr": "salarî",
"sem-srb": "erebiya başûrî ya kevn",
"pad": "paumarî",
"yap": "yapî",
"xul": "ngunawalî",
"kzj": "kadazaniya beravî",
"fan": "fangî (Bantu)",
"brx": "bodoyî (Hindistan)",
"ane": "xârâcùù",
"cro": "crowî",
"ram": "kanelayî",
"tea": "temiarî",
"cnk": "khumi çinî",
"tkr": "tsakhurî",
"ulk": "meryamî",
"tzh": "tzeltalî",
"avk": "kotavayî",
"mjg-mgr": "mangghuerî",
"xav": "xavanteyî",
"kmv": "kreyoliya fransî ya karipúna",
"aas": "aasayî",
"guz": "gusiiyî",
"erk": "efateyiya başûr",
"ngh": "nlûyî",
"cel-gau": "gaulisî",
"hz": "hereroyî",
"alq": "algonquinî",
"cay": "cayugayî",
"inz": "înesenyoyî",
"sgr": "sangisarî",
"iro-min": "mingoyî",
"dru": "rukaiyî",
"tta": "tuteloyî",
"lui": "luiseñoyî",
"mgm": "mambayî",
"xpm": "pumpokolî",
"kyj": "karaoyî",
"ksd": "tolaiyî",
"pkc": "baekceyî",
"pcm": "pidgîniya nîjeryayî",
"kjg": "khmuyî",
"cow": "cowlitzî",
"cyo": "cuyunonî",
"msn": "vuresî",
"jhi": "jehaiyî",
"map-bms": "banyumasanî",
"fla": "montana salişî",
"skd": "sierra miwokiya başûrî",
"xpu": "punîkî",
"tsd": "tsakonî",
"gue": "gurindjiyî",
"ksi": "krisayî",
"pni": "aohengî",
"par": "panamintî",
"ulb": "ulukwumî",
"miq": "miskitoyî",
"mcb": "maçiguengayî",
"kls": "kalaşayî",
"sdo": "bukar-sadung bidayuhî",
"nr": "ndebeliya başûrî",
"mch": "maquiritarî",
"msb": "masbatenyoyî",
"tsi": "tsimşî",
"gmq-gut": "gutnişî",
"tus": "tuskarorayî",
"lmw": "miwokiya golî",
"srs": "sarceeyî",
"zag": "zaghawayî",
"klb": "kiliwayî",
"kmc": "kamiya başûrî",
"beg": "belaîtî",
"inc-ash": "prakrîtiya aşokanît",
"kj": "kwanyamayî",
"ng": "ndongayî",
"irk": "îraqwî",
"grk-ita": "grîkoyî",
"aaq": "penobskotî",
"kek": "q'eqçiyî",
"ojb": "ojibweyiya bakurî",
"txu": "kayapó",
"one": "oneidayî",
"ktv": "katuyiya rojhilatî",
"nhe": "nahwatliya huastekaya rojhilatî",
"lwl": "lawayiya rojhilatî",
"lua": "luba-kasayî",
"bjb": "barngarla",
"acu": "açuarî",
"cof": "tsafikiyî",
"gwi": "gwiçinî",
"urk": "urak lawoi'",
"tar": "tarahumarayiya navendî",
"kva": "bagvalalî",
"hni": "haniyî",
"sek": "sekanî",
"cpg": "yûnaniya kapadokyayî",
"wmb": "wambayayî",
"xld": "lîdyayî",
"amn": "amanabî",
"haa": "hänî",
"cdy": "çadongî",
"akr": "arakî",
"kju": "kaşayayî",
"ada": "adangmeyî",
"alh": "alawayî",
"gdq": "mehriyî",
"tmo": "temoqî",
"kus": "kusaalî",
"dwu": "dhuwalî",
"pak": "parakanã",
"agg": "angorî",
"wao": "wappoyî",
"aji": "ajië",
"cas": "tsimaneyî",
"byq": "basayî",
"xls": "lusîtanî",
"gcr": "kreyoliya guwiyanîsî",
"apk": "apaçiya deştan",
"kut": "kutenaiyî",
"gmw-jdt": "danmarkiya jersey",
"hus": "wastekî",
"rej": "rejangî",
"dog": "dogonî",
"roa-lor": "lorrainî",
"emb": "embalohî",
"tnq": "taînoyî",
"crj": "kriya rojhilat ya başûrî",
"nuj": "nyolî (Uganda)",
"gmq-oda": "danmarkiya kevn",
"akv": "axvaxî",
"doi": "dogriyî",
"trp": "kokborokî",
"geh": "hutterisçî",
"gul": "gullahî",
"coo": "comoxî",
"ije": "biseniyî",
"itk": "îtaliya cihû",
"rmc": "romaniya karpatyayî",
"gmw-gts": "gottscheerîşî",
"ttg": "tutongî",
"ngu": "nahwatliya guerrero",
"pse": "malayiya navendî",
"onw": "nubiya kevn",
"qui": "kuwileytî",
"giw": "gelaoyiya spî",
"ojw": "ojibweyiya rojavayî",
"gay": "gayoyî",
"rmw": "romaniya welşî",
"nhw": "nahwatliya huastecaya rojavayî",
"xkl": "bakungî",
"loz": "loziyî",
"yij": "yindcîbarndî",
"kry": "krytsî",
"ple": "palueyî",
"tht": "tahltanî",
"mkq": "bay miwokî",
"sks": "maiayî",
"mhe": "besisî",
"mrv": "mangarevanî",
"nrm": "naromî",
"chp": "çîpewî",
"bll": "biloxî",
"knx": "kendayanî",
"khw": "khowarî",
"dua": "diwalayî",
"roa-cha": "şampanyî",
"gug": "guaraniyiya paraguayanî",
"dsn": "dusnerî",
"tmh": "tuaregî",
"pmf": "pamonayî",
"smq": "samoyî",
"tal": "talî",
"yuc": "yuçî",
"mqy": "manggarayî",
"bts": "batakiya simalungun",
"tos": "totonakiya çiyayî",
"oia": "oyratayî",
"blz": "balantak",
"bjz": "baruga",
"kai": "karekareyî",
"zav": "zapotekiya yatzaçî",
"okd": "okodiayî",
"ndd": "nde-nsele-nta",
"omc": "moçikayî",
"bto": "iriga bikolanoyî",
"okr": "kirikeyî",
"top": "totonakiya papantla",
"xer": "xerenteyî",
"xlc": "lîkî",
"mid": "mandaicî",
"suy": "suyayî",
"chz": "ozumacin çinantecî",
"pah": "tenharimî",
"til": "tillamookî",
"afn": "defakayî",
"guc": "wayuuyî",
"ojp": "japoniya kevn",
"lif": "limbuyî",
"efi": "efikî",
"mek": "mêkêoyî",
"ayl": "erebiya lîbyayî",
"bnt-phu": "phuthî",
"kne": "kankanaeyî",
"ktn": "karitiyanayî",
"abl": "abûngî",
"rtm": "rotumanî",
"wyb": "ngiyambaayî",
"crg": "mîçîfî",
"kxu": "kuiyî (Hindistan)",
"cts": "bicolanoyiya northern catanduanes",
"dje": "zarmayî",
"plw": "palawanoyî",
"xlu": "luwiyanî",
"koy": "koyukonî",
"vmw": "makhuwayî",
"vmf": "frankiya rojhilatî",
"bru": "bruyiya rojhilatî",
"kmb": "kimbunduyî",
"xsa": "sabaeanî",
"tbk": "tagbanwayiya kalamî",
"kzt": "tambunan dusunî",
"xng": "mongoliya navîn",
"acn": "açangî",
"bwx": "bu-nao bunuyî",
"cwg": "çewongî",
"vmb": "mbabaramî",
"ijs": "îjoyiya başûr-rojhilat",
"asb": "assînîboyî",
"nzd": "nzadî",
"itv": "îtawitî",
"lvk": "lavukaleveyî",
"bde": "badeyî",
"tbc": "takyayî",
"aby": "aneme wakeyî",
"urb": "urubú-kaaporî",
"coh": "çiçonyi-çidzihana-çikauma",
"dbj": "ida'anî",
"smk": "bolinaoyî",
"lbx": "lawanganî",
"kfr": "kaççiyî",
"hlt": "nga layî",
"agm": "angaatahayî",
"xpg": "firîgî",
"smr": "simeulueyî",
"vma": "martuthunirayî",
"gub": "guajajarayî",
"chf": "tabasco çontalî",
"mqs": "makyaniya rojava",
"iby": "îbaniyî",
"cng": "qiangiya bakurî",
"jac": "jakaltekî",
"trw": "torwalî",
"lti": "lêtîyî",
"scl": "şinayî",
"gwr": "gwereyî",
"dgc": "casiguran dumagat agtayî",
"btm": "batakiya mandaîlîng",
"puj": "punan tubuyî",
"oka": "okanaganî",
"gek": "gerkayî",
"mat": "matlatzincayî",
"kio": "kiowayî",
"cku": "koasatî",
"kms": "kamasauyî",
"xaa": "erebiya endulusî",
"gut": "maleku jaikayî",
"aou": "a'ou",
"elx": "îlamî",
"peh": "bonanî",
"pgl": "îrlendiya prîmîtîv",
"myx": "masabayî",
"spp": "supyireyî",
"xnt": "narragansettî",
"cad": "caddoyî",
"aap": "pará arárayî",
"rme": "angloromanî",
"ura": "urarinayî",
"mpm": "mixteciya yosondúa",
"poe": "popolocayiya san juan atzingo",
"sgd": "surigaononî",
"kvh": "komodoyî",
"xtc": "kaduglî",
"tjg": "tunjungî",
"azg": "san pedro amuzgos amuzgo",
"uun": "pazehî",
"udl": "uldemeyî",
"skp": "sekapanî",
"isi": "nkem-nkumî",
"wrz": "warrayî",
"its": "îsekiriyî",
"tgb": "tobilungî",
"bdy": "bandjalangî",
"ssb": "samayiya başûrî",
"hnn": "hanunooyî",
"enn": "engenniyî",
"srm": "saramakkanî",
"dmr": "damariya rojhilat",
"djo": "jangkangî",
"kij": "kîlîvîlayî",
"rob": "tae'",
"sad": "sandaweyî",
"har": "harariyî",
"hov": "hovonganî",
"pos": "popolucayiya sayula",
"sit-bok": "bokarî",
"kns": "kensiuyî",
"mbd": "dibabawon manoboyî",
"asu": "tocantins asurînî",
"sjw": "şawneeyî",
"btw": "butuanonî",
"jut": "jutişî",
"bgs": "tagabawayî",
"xsb": "sambalî",
"mkf": "miyayî",
"mrn": "çeke holoyî",
"kui": "kuikúroyî",
"hup": "hupayî",
"abm": "abanyomî",
"ukk": "muak sa-aakî",
"lbk": "bontociya navendî",
"dbp": "duwaiyî",
"jup": "hupdëî",
"cce": "çopiyî",
"muy": "muyangî",
"agt": "agtayiya kagayanî ya navendî",
"sxn": "sangirî",
"brc": "kreyoliya holendî ya berbiceyî",
"yux": "yukaghiriya başûrî",
"zpv": "zapotekiya çiçicapan",
"teo": "atesoyî",
"dif": "dieriyî",
"maq": "mazateciya çiquihuitlan",
"sgh": "şughnî",
"gnc": "guwançî",
"ega": "egayî",
"kbh": "camsayî",
"bni": "bangî",
"tdj": "tajioyî",
"ngi": "ngizimî",
"bue": "beothukî",
"pok": "pokangayî",
"myw": "muyuwî",
"bkn": "bukîtan",
"dyu": "dyulayî",
"asn": "xingú asuriní",
"cme": "cermayî",
"tna": "takanayî",
"mia": "miamî",
"epi": "epieyî",
"mcq": "eseyî",
"kyu": "kayahiya rojavayî",
"sai-pyk": "pykobjê",
"zaa": "zapotekiya sierra de juarez",
"dhv": "dehûyî",
"bxk": "bukusuyî",
"nuz": "nahwatliya tlamacazapa",
"trm": "tregamî",
"cps": "capiznonî",
"slm": "pangutaran samayî",
"cia": "cia-ciayî",
"abv": "erebiya bahranî",
"lod": "berawanî",
"mhj": "mogholî",
"mnv": "rennelleseyî",
"gir": "gelaoyiya sor",
"bfq": "badagayî",
"wyi": "woiwurrungî",
"eya": "eyakî",
"awk": "awabakal",
"zoc": "zoqueyiya copainala",
"smw": "sumbawayî",
"ank": "goemayî",
"mbl": "maxakalî",
"tbd": "kakî aeyî",
"nez": "nez perceyî",
"crn": "corayî",
"sai-tap": "tapayunayî",
"see": "senekayî",
"huv": "huaveyî",
"apn": "apinayé",
"mfa": "malayiya pattani",
"crx": "karriyerî",
"mam": "mamî",
"ibl": "îbaloiyî",
"cac": "çujî",
"kdt": "kuyî",
"ocu": "tlahuicayî",
"hvn": "sabuyî",
"cjp": "cabecarî",
"xbi": "kombioyî",
"xra": "krahô",
"blc": "bella koola",
"peq": "pomoyiya başûrî",
"bpz": "bilbayî",
"mzp": "movimayî",
"lhu": "lahuyî",
"kge": "komeringî",
"tue": "tuyukayî",
"taa": "tananaya jêrîn",
"vkp": "kreyoliya portugalî ya korlai",
"cmo": "mnongiya navendî",
"mov": "mojaveyî",
"kkz": "kaskayî",
"zro": "zaparoyî",
"mnb": "munayî",
"mwf": "murînpatayî",
"zkg": "goguryeoyî",
"kiw": "kiwaiyiya bakur-rojhilat",
"ebr": "ebrieyî",
"pmt": "tuamotuanî",
"roa-ang": "angevinî",
"ssg": "seimatî",
"hch": "huiçolî",
"bew": "betawî",
"ycn": "yucunayî",
"mbr": "nukak makuyî",
"apq": "a-pucikwar",
"nym": "nyamwezî",
"tbi": "gaamî",
"obi": "obispeñoyî",
"pri": "paicî",
"lu": "luba-katangayî",
"kcg": "tyapî",
"yui": "yurutî",
"kvm": "kendemî",
"djk": "aukanî",
"rir": "ribunî",
"lrg": "laragiayî",
"wnw": "wintuyî",
"woe": "woleayî",
"ljl": "li’oyî",
"kic": "kîkapûyî",
"ndh": "ndalî",
"hdy": "hadiyyayî",
"cbs": "caşinahuayî",
"kys": "baram kayanî",
"nxg": "ngadhayî",
"hix": "hixkaryanayî",
"mpe": "majangî",
"pwm": "molbogî",
"nyn": "nyankoleyî",
"ilb": "îlayî",
"qya": "qiwenyayî",
"wrk": "garawayî",
"def": "dezfuliyî",
"oym": "wayampî",
"rkb": "rikbaktsayî",
"sai-msk": "masakarayî",
"itz": "îtzayî",
"sgc": "kipsigisî",
"ppi": "paipaî",
"tji": "tujiayiya bakurî",
"yak": "yakîmayî",
"gvp": "para gaviãoyî",
"jmc": "maçameyî",
"bfs": "bayiya başûrî",
"ncb": "nicobariya navendî",
"kvb": "kubuyî",
"veo": "venturenyoyî",
"poi": "popolucayiya highland",
"bny": "bintuluyî",
"bhp": "bîmayî",
"rim": "nyaturuyî",
"sog": "sogdî",
"rth": "ratahanî",
"mse": "museyî",
"ncl": "nahwatliya mîçoakanê",
"acp": "akîpayiya rojhilatî",
"pao": "paiuteyiya bakurî",
"iyx": "yaayî",
"zhx-tai": "taişanîzî",
"coe": "koreguajeyî",
"ser": "serranoyî",
"nko": "nkonyayî",
"mgq": "malilayî",
"ppo": "folopayî",
"alc": "kawésqar",
"abg": "abagayî",
"wro": "wororayî",
"kre": "panarayî",
"tuv": "turkanayî",
"dav": "taitayî",
"ppk": "umayî",
"roa-oca": "katalaniya kevn",
"tso-lya": "çolyanî",
"myu": "mundurukú",
"dcc": "deccaniyî",
"lsi": "laşiyî",
"ugo": "gongî",
"nab": "nambikwarayî",
"lil": "lillooetî",
"gqi": "guiqiongî",
"gah": "alekanoyî",
"ncz": "natçezî",
"xom": "komoyî",
"prk": "paraukî",
"bea": "beaverî",
"sky": "sikayanayî",
"pui": "puinaveyî",
"kgg": "kusundayî",
"lml": "ragayî",
"ore": "orejónî",
"vbb": "babariya başûr-rojhilat",
"mbp": "wiwayî",
"bft": "baltî",
"lgg": "lugbarayî",
"mxd": "modangî",
"mir": "isthmus mixeyî",
"enf": "forest enetsî",
"pkp": "pukapukanî",
"kzi": "kelabît bariyoyî",
"wba": "waraoyî",
"xeg": "ǁxegwî",
"cpa": "palantla çinantecî",
"men": "mendeyî",
"btu": "batuyî",
"psy": "piscatawayî",
"xhu": "hurî",
"for": "foreyî",
"deg": "degemayî",
"sai-oto": "otomacoyî",
"nhv": "nahwatliya temascaltepec",
"gme-cgo": "gothîkiya krimî",
"kyi": "kîputî",
"olt": "lîtwanyayiya kevn",
"alp": "aluneyî",
"nbb": "ndoeyî",
"uvl": "loteyî",
"pab": "parecî",
"nbh": "ngamoyî",
"dra-okn": "kannadayiya kevn",
"aak": "ankaveyî",
"wiv": "vîtûyî",
"kuz": "kunzayî",
"atb": "zaiwa",
"aca": "açaguwayî",
"zmr": "maranungkuyî",
"xke": "kerehoyî",
"isk": "îşkaşimiyî",
"dig": "digoyî",
"agn": "agutaynenî",
"bji": "burjî",
"klw": "linduyî",
"bwi": "baniwayî",
"ygr": "yagariayî",
"mez": "menomineeyî",
"kag": "kajamanî",
"mey": "hassaniyayî",
"umo": "umotinayî",
"yii": "yidinyî",
"twy": "tawoyanî",
"ktw": "katoyî",
"ari": "arîkara",
"ysr": "sirenikî",
"adz": "adzerayî",
"spo": "spokanî",
"cgg": "rukîgayî",
"wuv": "wuvulu-auayî",
"etu": "ejaghamî",
"jmd": "yamdenayî",
"mhy": "ma'anyanî",
"tae": "taryanayî",
"kzr": "karangî",
"zhn": "nong zhuangî",
"ess": "yupikiya sîberyaya navendî",
"teg": "teke-tegeyî",
"cqd": "çuanqiandian cluster miaoyî",
"sri": "sirianoyî",
"dag": "dagbaniyî",
"gae": "warekenayî",
"lre": "laurentî",
"tbt": "kitemboyî",
"pgn": "paelignianî",
"onb": "lingaoyî",
"pth": "pataxó hã-ha",
"vmg": "minigirî",
"yah": "yazghulamî",
"kxn": "kanowitî",
"kde": "makondeyî",
"api": "apiaká",
"och": "çîniya kevn",
"pre": "prînsîpenî",
"mgr": "mambwe-lunguyî",
"stp": "tepehuaniya başûr-rojhilat",
"kak": "kayapa kallahanî",
"myg": "mantayî",
"kee": "keresiya rojhilatî",
"mfh": "matalî",
"bkr": "bakumpaî",
"alw": "alaba",
"pml": "sabirî",
"rag": "logûlî",
"mei": "midobî",
"mjy": "mahicanî",
"blm": "belî",
"nhu": "nooneyî",
"kuf": "katuyiya rojavayî",
"cux": "tepeuxila cuicatecî",
"nyy": "nyakyusayî",
"mtf": "murikî (Gîneya nû)",
"mrt": "margî",
"mpj": "martu wangkayî",
"tlh": "klîngonî",
"enh": "tundra enetsî",
"sai-crd": "coroado purî",
"umi": "ukitî",
"tcb": "tanakrosî",
"kpt": "karatayî",
"xvn": "vandalî",
"xsm": "kasemî",
"ltc": "çîniya navîn",
"yuz": "yuracareyî",
"bhw": "biyakî",
"sqt": "soqotrî",
"dny": "deniyî",
"lcp": "lawayiya rojavayî",
"tpx": "akatepekiya me'phaayî",
"ake": "akawaîo",
"jeb": "jeberoyî",
"gup": "gunwingguyî",
"xeb": "eblaiteyî",
"zkt": "xîtanî",
"lex": "luangî",
"mnq": "minriqî",
"njm": "angamî",
"sge": "segayî",
"hoi": "holîkaçukî",
"tee": "huehuetla tepehuayî",
"bil": "bileyî",
"gej": "genî",
"wim": "wik-mungkanî",
"oyd": "oydayî",
"gvj": "guajayî",
"blw": "balangao",
"lgt": "pahîyî",
"sai-cac": "cacanî",
"xok": "xoklengî",
"knv": "taboyî",
"slc": "salibayî (Kolombiya)",
"noz": "nayî",
"bnq": "bantîkî",
"ykm": "yakamulî",
"dym": "yanda dogonî",
"cri": "sãotomenseyî",
"shu": "erebiya çadî",
"tcs": "creoleyiya torres strait",
"gmw-stm": "sathmar swabî",
"rbl": "miraya bikolî",
"mbn": "macaguanî",
"mgu": "mailuyî",
"pib": "yineyî",
"yku": "kuamasî",
"gva": "kaskihayî",
"sai-yur": "yurumanguî",
"clw": "çulymî",
"bln": "bîkolanoyiya başûr katanduanî",
"mxi": "mozarabîkî",
"sob": "sobeyî",
"agd": "agarabiyî",
"bxe": "ongotayî",
"ito": "îtonamayî",
"mmq": "aisî",
"gnd": "zulgo-gemzekî",
"wig": "wik-ngathanayî",
"dto": "tommo soyî",
"suz": "sunwarî",
"rol": "romblomanonî",
"llu": "lawî",
"jaa": "jamamadiyî",
"bns": "bundelî",
"wal": "wolayttayî",
"oui": "oyguriya kevn",
"bjt": "balanta-ganjayî",
"alk": "alakî",
"nhr": "naroyî",
"gmw-vog": "almaniya volga",
"nsn": "nehanî",
"kqe": "kalaganî",
"end": "endeyî",
"jui": "ngadcurî",
"hay": "hayayî",
"svb": "ulau-suainî",
"saz": "sauraştrayî",
"mep": "miriwungî",
"ert": "eritaiyî",
"yen": "yendangî",
"mxb": "mixteciya tezoatlan",
"pmw": "plains miwokî",
"fak": "fangî (Beboid)",
"sai-bet": "betoî",
"arl": "arabela",
"cks": "tayoyî",
"nrn": "nornî",
"kao": "kassonkeyî",
"blb": "bilua",
"wab": "wabî",
"kwd": "kwaioyî",
"abr": "abronî",
"wbi": "wanjî",
"cso": "soçiapam çinantecî",
"kmo": "kwomayî",
"ddw": "dawera-daweloorî",
"zae": "zapotekiya yareni",
"rac": "rasawayî",
"bra": "brajî",
"naz": "nahwatliya koatepek",
"qwm": "kipçakî",
"ayz": "maybratî",
"amp": "alamblakî",
"sit-liz": "lizuyî",
"jum": "jumjumî",
"tub": "tübatulabalî",
"hih": "pamosuyî",
"kbc": "kadiweuyî",
"tun": "tunikayî",
"cod": "cocamayî",
"abt": "ambulasî",
"ame": "yaneşa'",
"xed": "hdî",
"yrb": "yarebayî",
"mbc": "macuşî",
"sai-mal": "malalî",
"khs": "kasuayî",
"soz": "temî",
"mva": "manamî",
"bcu": "bîlîwayî",
"zpo": "zapotekiya amatlan",
"tfr": "teribeyî",
"xgf": "gabrielino-fernandeñoyî",
"aah": "abu' arapeşî",
"adn": "adangî",
"btn": "ratagnonî",
"gby": "gbariyî",
"sxb": "subayî",
"onn": "onobasuluyî",
"aih": "aî-çamî",
"djm": "jamsayî",
"wun": "bunguyî",
"bhv": "bahauyî",
"ahk": "akha",
"tow": "jemezî",
"duw": "dusun wituyî",
"alj": "alanganî",
"sbk": "safwayî",
"kho": "xotanî",
"ndj": "ndambayî",
"mmh": "mehinakuyî",
"ttw": "long watî",
"sai-cat": "catacaoyî",
"kck": "kalangayî",
"yav": "yangbenî",
"heh": "heheyî",
"szw": "sawayî",
"phk": "phakeyî",
"aoa": "angolar",
"xur": "urartûyî",
"txn": "taranganiya rojava",
"piv": "pilenî",
"xta": "mixtekiya alcozauca",
"ute": "uteyî",
"tad": "tauseyî",
"yug": "yugî",
"zum": "kumzarî",
"nkr": "nukuoroyî",
"xaw": "kawaiisuyî",
"mpa": "mpotoyî",
"zng": "mangî",
"gad": "gaddangî",
"drn": "damariya rojava",
"oca": "ocainayî",
"mcg": "mapoyoyî",
"gbm": "garhwaliyî",
"sbh": "sorî-harenganî",
"kew": "kewayî",
"mki": "dhatkî",
"obt": "bretoniya kevn",
"alo": "larike-wakasihu",
"drl": "baagandjiyî",
"tog": "tongayî (Malawî)",
"tdl": "surî",
"met": "matoyî",
"cle": "lealao çinantecî",
"gmq-jmk": "jamtîşî",
"gvf": "golinî",
"tlc": "totonakiya misantla",
"bfg": "busang kayanî",
"kvc": "kovêyî",
"kay": "kamayurayî",
"hei": "heiltsukî",
"urt": "uratî",
"nll": "nihalî",
"bdp": "bendeyî",
"ybe": "yuguriya rojavayî",
"nbr": "numanayî",
"mht": "mandahuacayî",
"wap": "wapişanayî",
"dzg": "dazagayî",
"wrg": "warunguyî",
"tiv": "tivî",
"tox": "tobî",
"kvo": "dobelî",
"ubl": "buhi'non bikolî",
"aja": "ajayî",
"toi": "tongayî (Zambiya)",
"sus": "susuyî",
"nhm": "nahwatliya morelos",
"mcf": "matsesî",
"lew": "ledo kailiyî",
"ckx": "cakayî",
"bxg": "bangalayî",
"nem": "nemî",
"mgo": "meta'",
"eno": "engganoyî",
"crd": "coeur d'aleneyî",
"bdb": "basapî",
"cui": "cuibayî",
"emx": "erromintxelayî",
"bsw": "baysoyî",
"piw": "pimbweyî",
"nim": "nilambayî",
"mzs": "makanîzî",
"njo": "aoyî",
"duk": "uyajitayayî",
"old": "moçî",
"sam": "aramiya samarî",
"mlq": "malinkî",
"awd-kaw": "kawişanayî",
"mro": "mruyî",
"sjm": "mapunî",
"bot": "bongoyî",
"lay": "lama baiyî",
"aif": "agî",
"bks": "masbate sorsogon",
"ibd": "îwaidjayî",
"dob": "dobûyî",
"noa": "woun meuyî",
"too": "totonakiya xicotepec de juarez",
"duv": "duvleyî",
"bqo": "baloyî",
"mna": "mbulayî",
"pbh": "panareyî",
"cav": "cavineñayî",
"scg": "sanggauyî",
"con": "cofanî",
"ubr": "ubirî",
"ata": "pele-ata",
"ngj": "ngieyî",
"ers": "ersuyî",
"tro": "taraoyî",
"yki": "yokeyî",
"tya": "tauyayî",
"byk": "biaoyî",
"tuq": "tedagayî",
"xrn": "arinî",
"mhq": "mandanî",
"ktx": "kaxarariyî",
"beu": "blagarî",
"mtv": "asaro'oyî",
"zca": "zapotekiya coatecas altas",
"abd": "manîdeyî",
"imn": "îmondayî",
"ofo": "ofoyî",
"avv": "avá-kanoeîro",
"ree": "rejang kayanî",
"klr": "khalingî",
"dby": "dibiyasoyî",
"bzd": "bribriyî",
"iwm": "îwamî",
"bvk": "bukatî",
"mgw": "matumbî",
"aus-wul": "wulguruyî",
"mto": "totontepec mixeyî",
"inc-ogu": "gujaratiya kevn",
"kpm": "kohoyî",
"bgz": "banggaî",
"klg": "tagakaulu kalaganî",
"xab": "sambeyî",
"pnz": "panayî (Afrîkaya navendî)",
"snl": "sangilî",
"aoc": "pemon",
"cul": "culinayî",
"nnq": "ngindoyî",
"cji": "çamalalî",
"urh": "urhoboyî",
"hoc": "hoyî",
"yok-nvy": "yokutiya geliyê bakurî",
"nbp": "nnamî",
"qyp": "kuiripî",
"ski": "sikayî",
"whk": "wahau kenyahî",
"fos": "sirayayî",
"paa-nun": "nungonî",
"mtd": "mualangî",
"mrh": "mara çinî",
"xeu": "keoru-ahiayî",
"ass": "îpulo",
"ing": "deg xinagî",
"ajg": "adjayî",
"kjl": "khamî",
"wed": "wedauyî",
"mxz": "maselayiya navendî",
"miw": "akoyeyî",
"lmc": "limilnganî",
"bag": "tukî",
"bva": "bareinî",
"yal": "yalunkayî",
"asa": "pare",
"sai-col": "colanî",
"kdd": "yankunytjatjarayî",
"pne": "penaniya rojavayî",
"hna": "minayî",
"ayp": "erebiya mezopotamyayî ya bakur",
"can": "çambriyî",
"yey": "yeyî",
"szc": "semaq beriyî",
"byd": "benyadu'",
"yok-svy": "yokutiya geliyê başûrî",
"xet": "setayî",
"aro": "araona",
"ntw": "nottowayî",
"szv": "isubuyî",
"crw": "çrauyî",
"kyz": "kayabiyî",
"asl": "asîluluyî",
"poz-abi": "abay-sembwakî",
"tsx": "mubamî",
"ify": "keley-i kallahanî",
"srv": "waray sorsogonî",
"ong": "oloyî",
"mvm": "muyayî",
"lam": "lambayî",
"icl": "zimanê destan ê îzlandê",
"xww": "wemba-wembayî",
"ixl": "îxilî",
"ape": "bukiyîpî",
"rui": "rufijî",
"mhl": "mauwakeyî",
"lgr": "lengoyî",
"nmy": "namuyî",
"ncn": "naunayî",
"pez": "penaniya rojhilatî",
"mfg": "mixiforeyî",
"bei": "bekatî'",
"ydg": "yidghayî",
"cwd": "kriya woods",
"mmn": "mamanwayî",
"nai-kat": "kathlametî",
"cje": "çruyî",
"ark": "arîkapûyî",
"fut": "futuna-anîwayî",
"wad": "wandamenî",
"tew": "tewayî",
"nyo": "nyoroyî",
"bky": "bokyî",
"tjm": "timucuayî",
"ysg": "sonagayî",
"ogu": "ogbronuagumî",
"mnj": "munjî",
"gwd": "gawwadayî",
"fbl": "west albay bikolî",
"uiv": "îyiveyî",
"bkq": "bakairí",
"ibe": "akpesî",
"maa": "mazateciya san jerónimo tecóatl",
"jig": "jinguluyî",
"tvd": "tsuvadî",
"xod": "kokodayî",
"adw": "amondawayî",
"guu": "yanomamöî",
"srh": "sarikolî",
"nam": "ngan'gityemerrî",
"hto": "minica huitotoyî",
"yby": "yaweyuhayî",
"urz": "uru-eu-wau-wau",
"ewo": "ewondoyî",
"cal": "karolînî",
"arr": "arara-karo",
"yup": "yukpayî",
"iai": "îaaiyî",
"wbh": "wandayî",
"dmw": "mudburrayî",
"xel": "keloyî",
"rma": "ramayî",
"zwa": "zayî",
"emi": "mûssaû-emîrakî",
"lai": "lambyayî",
"dim": "dimeyî",
"agu": "agwakatekî",
"zin": "zinzayî",
"kiv": "kimbuyî",
"dbg": "dogul domî",
"bzg": "babuzayî",
"roa-ole": "leoniya kevn",
"bun": "şerbroyî",
"gnn": "gumatjî",
"mhs": "bûrî",
"pln": "palenqueroyî",
"aus-guw": "guwarî",
"tgt": "tagbanwayiya navendî",
"mwv": "mentawaî",
"kke": "kakabeyî",
"pla": "mianî",
"bpr": "koronadal blaanî",
"oty": "tamîliya kevn",
"ywr": "yawuruyî",
"nai-tap": "tapaçultecî",
"sai-sec": "seçurayî",
"pid": "piaroayî",
"mih": "mixteciya çayuco",
"icr": "kreyoliya îngilîzî ya îslanderê",
"hoh": "hobyótî",
"fab": "annoboneseyî",
"ssm": "semnamî",
"ncg": "nisga'ayî",
"mkg": "makî (Çîn)",
"max": "malayiya moluccana bakur",
"zak": "zanakî",
"kxv": "kuviyî",
"xkn": "kayan river kayanî",
"fuy": "fuyugî",
"zpl": "zapotekiya laçixio",
"kwf": "kwara'aeyî",
"aig": "îngilîziya kreyolî ya antîgua û barbud",
"rai": "ramoaainayî",
"otd": "dohoyî",
"bey": "akuwagelî",
"nwr": "nawaruyî",
"tmq": "tumleoyî",
"tgx": "tagişî",
"pbr": "pangwayî",
"loj": "louyî",
"emp": "emberayiya bakurî",
"nho": "takuuyî",
"bco": "kalulî",
"ifb": "batad ifugaoyî",
"jao": "yanyuwayî",
"inc-ohi": "hindiya kevn",
"cup": "cupeñoyî",
"bci": "bawuleyî",
"ngo": "ngonî",
"nun": "anongî",
"yee": "yimasî",
"kxb": "krobuyî",
"hub": "huambisayî",
"nee": "kumakî",
"bec": "iceve-macî",
"smb": "simbarî",
"kuj": "kuriayî",
"ian": "îatmulî",
"cjv": "çuaveyî",
"zne": "zandeyî",
"ssd": "siroî",
"cbn": "nyah kurî",
"lwg": "wangayî",
"bmr": "muinanî",
"kya": "kwayayî",
"xem": "kembayanî",
"acz": "açeronî",
"pav": "wari'",
"egx-dem": "demotîkî",
"bph": "botlixî",
"lev": "lammayî",
"btp": "budibudî",
"bzq": "bulî",
"tdv": "toroyî",
"klq": "rumuyî",
"ojv": "javayiya ontongê",
"bdi": "burunî",
"wya": "wyandotî",
"pbb": "paezî",
"lfn": "lingua franca nova",
"jit": "jitayî",
"amq": "amahaî",
"tcf": "mepaya malînaltepekî",
"ebu": "embuyî",
"rwm": "ambayî",
"pnh": "penrhynî",
"and": "ansus",
"pej": "pomoyiya bakurî",
"kue": "kumanî",
"fip": "fipayî",
"kcn": "nubiyî",
"lol": "mongoyî",
"bwd": "biwaydokayî",
"aia": "arosî",
"zau": "zangskarî",
"pud": "punan aputî",
"ikz": "îkizuyî",
"bgb": "bobongko",
"thk": "tharakayî",
"ymc": "mujiya başûrî",
"mny": "manyawayî",
"xwa": "kwazayî",
"wrs": "warisî",
"nai-tip": "tipaî",
"teu": "sooyî",
"ked": "kereweyî",
"szp": "suaboyî",
"rgr": "resigaroyî",
"bdm": "budumayî",
"nib": "nakameyî",
"lek": "leyponî",
"guh": "guahiboyî",
"bqr": "burusuyî",
"apl": "lipan",
"bnd": "banda",
"tsj": "tşanglayî",
"xme-old": "mediya kevn",
"ppn": "papapanayî",
"mwp": "kala lagaw yayî",
"bfa": "bariyî",
"plh": "paulohî",
"btd": "batakiya dairî",
"ure": "uruyî",
"sty": "tatariya sîberî",
"buy": "mmaniyî",
"xyy": "yorta yortayî",
"xdy": "malayîk dayakî",
"kmj": "kumarbhag pahariayî",
"vun": "vunjoyî",
"cuh": "çukayî",
"lgl": "walayî",
"pna": "punan bah-biauyî",
"now": "nyamboyî",
"gqa": "ga'andayî",
"thm": "thavungî",
"acr": "açiyî",
"cir": "tiriyî",
"mxr": "murîkî (Malezya)",
"swp": "suauyî",
"zor": "zoqueyiya rayón",
"wic": "wiçitayî",
"suw": "sumbwayî",
"bum": "buluyî (Kamerûn)",
"rnw": "rungwayî",
"nak": "nakanayî",
"trs": "trîkuyiya çicahuaxtla",
"lpo": "lipoyî",
"gwe": "gwenoyî",
"cbd": "carijonayî",
"mot": "barî",
"tlv": "taliabuyî",
"xsu": "sanumayî",
"sjl": "mijî",
"pip": "peroyî",
"acx": "erebiya ummanî",
"ybb": "yembayî",
"uln": "unserdeutsch",
"yil": "yindjilandjî",
"ykn": "kua-nsî",
"apu": "apurinã",
"sso": "sissanoyî",
"puc": "punan merapî",
"mwn": "nyamwangayî",
"bpa": "dakakayî",
"mmd": "maonanî",
"bof": "bolonî",
"ati": "attié",
"wji": "warjî",
"lra": "rara bakati'",
"mlu": "toabaîtayî",
"bdd": "bunamayî",
"bfd": "bafutî",
"keu": "akebuyî",
"wod": "wolanî",
"ksb": "şambalayî",
"mff": "nakî",
"azn": "durango nahuatliya rojavayî ya jêrîn",
"asi": "buruwayî",
"ala": "alago",
"tpy": "trumaî",
"zms": "mbesayî",
"kib": "koalibî",
"jun": "juangî",
"guf": "gupapuynguyî",
"liy": "banda-bambariyî",
"sai-pam": "pamiguayî",
"loa": "lolodayî",
"elm": "elemeyî",
"aad": "amalî",
"lcm": "tungagî",
"twh": "tai dónî",
"ppm": "papumayî",
"xmr": "meroytîkî",
"kno": "konoyî (sierra leone)",
"kzz": "kalabrî",
"emw": "emplawasî",
"pnk": "pawnakayî",
"gym": "ngäbereyî",
"mxj": "mijuyî",
"lun": "lundayî",
"kos": "kosraeyî",
"kzx": "kamarianî",
"aan": "anambeyî",
"ukq": "ukwayî",
"tau": "tananaya jorîn",
"anc": "ngas",
"rcf": "kreyola fransî ya réunion",
"knm": "kanamariyî",
"bvy": "baybayanonî",
"jnj": "yemsayî",
"onu": "unuayî",
"bdh": "tara bakayî",
"pij": "pijaoyî",
"mks": "mixteciya silacayoapan",
"juh": "honeyî",
"etx": "etenî",
"sai-chr": "çarruayî",
"aui": "anukî",
"cuc": "usila çinantecî",
"xme-ott": "tatiya kevn",
"njj": "njenî",
"mng": "mnongiya rojhilatî",
"cih": "çinaliyî",
"ard": "arabana",
"val": "vehesî",
"aos": "taykatî",
"peb": "pomoyiya rojhilatî",
"xcb": "kumbrîkî",
"clk": "iduyî",
"aem": "aremî",
"vut": "vuteyî",
"teq": "temeinî",
"siu": "sinagenî",
"xmm": "manado malayî",
"gdj": "kurtjarî",
"xau": "kauwerayî",
"atz": "arta",
"zsr": "zapoteciya rincona başûrî",
"wmt": "walmajarrî",
"ter": "terenoyî",
"sit-sit": "situyî",
"zns": "mangasî",
"kwz": "kwadiyî",
"iki": "îkoyî",
"prq": "aşeninka pereneyî",
"ztq": "zapotekiya quioquitani-quieri",
"wan": "wanî",
"txh": "thrasî",
"ahg": "qimant",
"boe": "mundablî",
"gww": "kwiniyî",
"ior": "înorî",
"ply": "bolyuyî",
"avt": "au",
"zpu": "zapotekiya yalalagî",
"bgv": "warkay-bîpîmî",
"srb": "sorayî",
"huu": "murui huitotoyî",
"zmm": "marimanindjî",
"xme-ker": "kermanî",
"gbv": "gbanuyî",
"awe": "awetí",
"xzh": "zhang-zhungî",
"wow": "wawonî",
"tqo": "toaripî",
"syb": "subaneniya navendî",
"ril": "riangî",
"nqo": "n'koyî",
"laj": "langoyî (Uganda)",
"bbh": "buganî",
"orr": "orumayî",
"gju": "gojriyî",
"ddd": "dongotonoyî",
"yik": "dongşanba laloyî",
"sai-bag": "baguayî",
"tsr": "akeî",
"txe": "totolî",
"nal": "nalikî",
"bez": "kîbenayî",
"jeh": "jehî",
"sem-saf": "safaitîkî",
"mka": "mbreyî",
"nai-jum": "jumaytepequeyî",
"ebg": "ebughuyî",
"cbv": "cacuayî",
"lnl": "bandayiya navendî ya başûr",
"inc-kho": "xolosî",
"tzx": "tabriakî",
"sln": "salînanî",
"ncr": "ncaneyî",
"kqt": "kadazaniya çemê kliasê",
"cbr": "caşibo-cacataiboyî",
"trt": "tunggareyî",
"tyj": "tai doyî",
"kpr": "korafe-yeghayî",
"hmt": "hamtaiyî",
"csi": "coast miwokî",
"bbi": "barombî",
"tvk": "ambrîmiya başûr-rojhilat",
"ynd": "yandruwandhayî",
"sai-sap": "saparayî",
"mik": "mikasukî",
"kid": "koşinî",
"bya": "batakiya palawan",
"awd-ana": "anauyayî",
"yrw": "yarawatayî",
"tnx": "tanemayî",
"lme": "peveyî",
"nbm": "ngbaka ma'boyî",
"myy": "macunayî",
"cao": "çacoboyî",
"anp": "angîka",
"sim": "seimî",
"nha": "nhandayî",
"kqn": "kaondeyî",
"ecr": "eteocretanî",
"crv": "çaurayî",
"bef": "benabenayî",
"aud": "anuta",
"yiy": "yir-yorontî",
"usp": "uspantekoyî",
"sry": "serayî",
"njy": "njyemî",
"mod": "mobilianî",
"mmt": "malalamaî",
"leu": "karayî (Gîneya nû)",
"mbe": "molaleyî",
"txi": "ikpengî",
"snc": "sinaugoroyî",
"noc": "nukî",
"mle": "manambuyî",
"kyr": "kuruayayî",
"ivb": "îbatanî",
"sai-chu": "çuruyayî",
"lsr": "aruopî",
"zaq": "zapotekiya aloapam",
"tef": "teressayî",
"siy": "sivandî",
"nyb": "nyangboyî",
"mpi": "mpadeyî",
"tcq": "kayî",
"kbt": "gabadiyî",
"dka": "dakpayî",
"bff": "bofî",
"ahs": "aşeyî",
"zar": "zapotekiya rincón",
"xct": "tibetiya klasîk",
"afz": "obokuîtayî",
"mnf": "mundanî",
"mpw": "mapidianî",
"gza": "ganzayî",
"acv": "açumawî",
"zmu": "muruwarî",
"zmt": "maringarrî",
"lbc": "lakkiayî",
"gbg": "gbanziriyî",
"des": "desanoyî",
"kud": "awhelawayî",
"agc": "agatuyî",
"tob": "tobayî",
"suc": "subanoniya rojavayî",
"oni": "onînî",
"mne": "nabayî",
"itm": "îtu mbon uzoyî",
"ges": "geser-goromî",
"aza": "azhayî",
"nfl": "aiwooyî",
"tqp": "tomoipî",
"rof": "romboyî",
"ury": "oryayî",
"axg": "mato grosso arára",
"jax": "jambî malayî",
"gkn": "gokanayî",
"tbq-gkh": "gokhyî",
"bkz": "bungku",
"aik": "akeyî",
"tku": "totonaciya nekaksaya jorîn",
"sho": "şangayî",
"crp-rsn": "russenorskî",
"nss": "nalî",
"guo": "guayaberoyî",
"dun": "dusun deyahî",
"bsm": "busamiyî",
"ble": "balanta-kentohe",
"axx": "xaragure",
"sxg": "şixingî",
"apz": "safeyoka",
"awd-pai": "paikonekayî",
"muc": "mbu'",
"kgu": "kobolî",
"dbf": "edopiyî",
"bbp": "bandayiya rojavayî ya navendî",
"loe": "saluanî",
"nux": "mehekî",
"elk": "elkeiyî",
"yaf": "yakayî",
"tiz": "tai hongjinî",
"stu": "samtaoyî",
"nkg": "nekginî",
"koz": "korakî",
"aus-bra": "barranbinyayî",
"tlr": "talîsî",
"spc": "sapeyî",
"sai-pyg": "payaguayî",
"tbq-mil": "milangî",
"klp": "kamasayî",
"crz": "cruzeñoyî",
"boj": "ancamî",
"tan": "tangaleyî",
"svs": "savosavoyî",
"ppq": "peyî",
"ngg": "ngbaka manzayî",
"mpx": "misima-paneatî",
"kcp": "kangayî",
"cbi": "çaçiyî",
"bfc": "bayiya bakurî",
"yec": "yeniçeyî",
"sai-trr": "tarairiú",
"plg": "pilagayî",
"hoe": "horomî",
"yun": "benayî",
"any": "anyî",
"yis": "yisî",
"ush": "uşojoyî",
"tav": "tatuyoyî",
"sve": "serilî",
"gob": "playeroyî",
"nkz": "nkarî",
"moc": "mocovî",
"jko": "kuboyî",
"reg": "karayî (Tanzanya)",
"hul": "hulayî",
"chh": "çinookî",
"yub": "yugambalî",
"tbg": "tayrorayiya bakur",
"mdc": "maleyî",
"kuo": "kumukioyî",
"mvy": "indus kohistanî",
"foi": "foiyî",
"coz": "çoçotecî",
"bim": "bimobayî",
"agf": "argunî",
"kmx": "wabodayî",
"mtl": "montolî",
"kjr": "kuruduyî",
"kcb": "kawaçayî",
"ibn": "îbinoyî",
"scb": "çutî",
"atx": "arutanî",
"mvt": "mpotovoroyî",
"kxa": "kayrîrûyî",
"boq": "bogayayî",
"zpx": "zapotekiya san baltazar loxiça",
"wnc": "wantoatî",
"itd": "tidongiya başûrî",
"xog": "sogayî",
"ann": "obolo",
"unr": "mundarî",
"bzh": "mapos buangî",
"tye": "kyengayî",
"kyq": "kengayî",
"kwa": "dâwî",
"bou": "bondeyî",
"aul": "aulua",
"zty": "zapotekiya yatee",
"wrp": "waropenî",
"mmi": "musarî",
"buq": "bremî",
"auj": "awjîla",
"gtu": "aghu tharrnggalayî",
"zil": "zialoyî",
"tlb": "tobeloyî",
"mty": "nabî",
"lbz": "lardilî",
"gdd": "gêdagêdî",
"mdx": "dizinî",
"bgt": "bugotûyî",
"tpc": "azoyú me'phaayî",
"jmb": "zumbunî",
"tdn": "tondanoyî",
"rng": "rongayî",
"mtm": "matorî",
"djd": "jaminjungî",
"gol": "golayî",
"doz": "dorzeyî",
"nmk": "namakurayî",
"mnt": "maykulanî",
"lbl": "libon bikolî",
"gde": "gudeyî",
"bxs": "busamî",
"xmd": "mbudumî",
"kdq": "koçî",
"cah": "cahuaranoyî",
"bza": "bandiyî",
"aes": "alseayî",
"dsq": "tadaksahakî",
"tmj": "samarokenayî",
"att": "pamplona atta",
"xhd": "hadramî",
"brp": "barapasiyî",
"omw": "tairorayiya başûr",
"nif": "nekî",
"iyo": "mesakayî",
"luq": "lucumiyî",
"kqz": "koranayî",
"coc": "cocopayî",
"amj": "amdang",
"tlk": "talokî",
"spi": "saponî",
"nnf": "ngaingî",
"mlj": "miltuyî",
"luz": "luriya başûrî",
"xas": "kamassî",
"cua": "cuayî",
"afd": "andaî",
"yxl": "yarlî",
"sit-tgm": "tangamî",
"nga": "ngbakayî",
"mrg": "misingî",
"igm": "kanggapeyî",
"gop": "yeretuarî",
"myh": "makahî",
"kpg": "kapîngamarancîyî",
"ero": "horpayî",
"eto": "etonî (Kamerûn)",
"bcq": "bençî",
"srr": "sererî",
"mqz": "malasangayî",
"hui": "huliyî",
"cap": "çipayayî",
"uar": "tayrumayî",
"mox": "molimayî",
"nai-ipa": "ipaî",
"arh": "arhuako",
"wdj": "wadjiginyî",
"tem": "temneyî",
"snn": "sionayî",
"mxk": "monumboyî",
"kru": "kurukhî",
"inc-mas": "asamiya navîn",
"zoo": "zapotekiya asunción mixtepec",
"wme": "wambuleyî",
"pru": "puragî",
"qwt": "kwalhioqua-tlatskanaî",
"gbb": "kaytetyeyî",
"bux": "boghomî",
"atm": "ata",
"abn": "abuayî",
"zab": "zapotekiya san juan guelavia",
"wny": "wanyî",
"sai-qtm": "quitemoyî",
"mmf": "mundatî",
"gdm": "laalî",
"kxi": "keningau murutî",
"dwr": "dawroyî",
"bfw": "bondoyî",
"aux": "aurá",
"ttv": "titanî",
"nrz": "lalayî (Gîneya nû)",
"gbp": "gbaya-bossangoayî",
"awn": "awngî",
"zia": "ziayî",
"way": "wayanayî",
"sut": "subtiabayî",
"rog": "roglayiya bakurî",
"ono": "onondagayî",
"mbj": "nadëbî",
"mls": "masalitî",
"bbj": "ghomalayî'",
"dyi": "djiminiyî",
"bgg": "bugunî",
"ail": "aîmeleyî",
"wir": "wirafedî",
"cla": "ronî",
"ost": "osatuyî",
"kkr": "kir-balarî",
"dgb": "bunogeyî",
"yha": "bahayî",
"mcd": "şaranahuayî",
"rwk": "rwayî",
"ifu": "mayoyao ifugaoyî",
"lig": "ligbiyî",
"jek": "jeri kuoyî",
"ghs": "guhu-samaneyî",
"bdn": "baldemuyî",
"xkf": "xengxayî",
"tsb": "tsamaî",
"sle": "şolagayî",
"nai-spt": "sahaptinî",
"nas": "nasioyî",
"mhd": "mbuguyî",
"kfa": "kodavayî",
"jur": "jurúnayî",
"goa": "guroyî",
"efa": "efaiyî",
"aqn": "altaya bakurî",
"tjs": "tujyayiya başûrî",
"tdy": "tadyawanî",
"los": "loniuyî",
"jka": "kaerayî",
"afo": "eloyî",
"cba-cat": "catio çibçayî",
"bsn": "barasanayî",
"ama": "amanayé",
"sai-san": "sanavironî",
"prc": "paraçî",
"nej": "nekoyî",
"cms": "messapicî",
"kyl": "kalapuyayî",
"coj": "coçimiyî",
"byr": "baruyayî",
"aey": "ameleyî",
"aus-ynk": "yangkaalî",
"yta": "taluyî",
"pay": "peç",
"lse": "lusengoyî",
"cub": "cubeoyî",
"gac": "andamaniya mezin a têkel",
"yry": "yarluyandî",
"tgp": "tangoayî",
"mjt": "sawriya pahariayî",
"phl": "phalurayî",
"ngt": "ngeqî",
"xnr": "kangrî",
"ciy": "çaimayî",
"bca": "bayiya navendî",
"aog": "angoram",
"yrn": "yerongî",
"skz": "sekarî",
"pia": "pima bajoyî",
"mjb": "makaleroyî",
"hot": "hoteyî",
"cbt": "çayahuitayî",
"aor": "aore",
"uve": "uveyiya rojava",
"pbv": "pnarî",
"mkr": "malasî",
"qvy": "kueyuyî",
"nai-chi": "çiquimulillayî",
"bhi": "bhilalî",
"erg": "sieyî",
"plo": "popolucayiya oluta",
"aun": "molmo one",
"kun": "kunamayî",
"laz": "aribwatsayî",
"waj": "waffayî",
"xsi": "sioyî",
"ibr": "îbuoroyî",
"zaf": "zapotekiya ayoquesco",
"lbq": "wamparî",
"keg": "teseyî",
"nyt": "nyawaygî",
"akc": "mpur",
"mnx": "manikionî",
"kmq": "kwamayî",
"khz": "keaparayî",
"ilk": "îlongotî",
"gaj": "gadsupî",
"aqp": "atakapa",
"kgr": "abunî",
"zpf": "zapotekiya san pedro quiatoni",
"sok": "sokoroyî",
"odu": "odualî",
"muh": "mündü",
"lbb": "labelî",
"knl": "keninjalî",
"bna": "boneratî",
"auy": "auyana",
"ade": "adeleyî",
"zpw": "zapotekiya zaniza",
"opm": "oksapminî",
"mug": "musguyî",
"hob": "mariyiya awistronezî",
"ywl": "laluyiya rojavayî",
"ksx": "kêdangî",
"gbq": "gbaya-bozoumî",
"xbm": "bretoniya navîn",
"kmn": "awtuwî",
"ziw": "zigulayî",
"wbm": "wayî",
"tgo": "sudestî",
"opo": "opaoyî",
"lan": "laruyî",
"xuu": "khweyî",
"gid": "gidarî",
"bvj": "baanî",
"zzj": "zuojiang zhuangî",
"wpc": "wirö",
"rnn": "roonî",
"don": "dourayî",
"bhj": "bahingî",
"lib": "likumî",
"akj": "jeru",
"awd-gnu": "guinauyî",
"dux": "duunî",
"bqp": "busayî",
"nsk": "naskapî",
"mqb": "mbukoyî",
"kwl": "kofyarî",
"nai-jtp": "jutiapayî",
"grd": "guruntumî",
"llp": "efateyiya bakur",
"agy": "altaya başûrî",
"yar": "yabaranayî",
"khc": "tukang besiya bakur",
"sbl": "sambaliya botolan",
"crc": "lonwolwolî",
"xkg": "kagoroyî",
"hdn": "haidayiya bakurî",
"sai-ctq": "catuquinaruyî",
"bbb": "barayî",
"amr": "amarakaerî",
"yky": "yakomayî",
"bsx": "yangkamî",
"tmz": "tamanakuyî",
"zsa": "sarasirayî",
"nig": "ngalakanî",
"khl": "lusiyî",
"xow": "kowakî",
"hem": "hembayî",
"pga": "erebiya jubayê",
"bhm": "batharî",
"bzb": "andioyî",
"yko": "yasayî",
"sax": "sayî",
"ypg": "pholayî",
"nnh": "ngiemboonî",
"mpd": "maçinereyî",
"vic": "kreyoliya giravên virginê",
"kdp": "kaningdon-nindemî",
"sit-cha": "çairelî",
"yes": "yeskwayî",
"uur": "urayî (Vanuatu)",
"mhu": "taraonî",
"slu": "selaruyî",
"xtd": "mixtekiya diuxi-tilantongo",
"utr": "etuloyî",
"bnv": "benerafî",
"ydk": "yoidikî",
"trr": "tauşîroyî",
"srk": "serudung murutî",
"xpo": "poçutekî",
"nkx": "nkorooyî",
"kes": "kugboyî",
"kgm": "karipúnayî",
"cax": "çiquitanoyî",
"big": "biangaî",
"vil": "vilelayî",
"dtu": "tebul ure dogonî",
"tty": "sikaritaî",
"rmg": "norweciya traveller",
"mlw": "molokoyî",
"biq": "bipî",
"ylu": "aribwaungî",
"wga": "wagayayî",
"tnz": "ten'ednî",
"qum": "sipakapenseyî",
"pue": "puelçeyî",
"nop": "numanggangî",
"mzq": "mori atasî",
"awd-kus": "kustenauyî",
"idi": "îdî",
"kzf": "da'a kailiyî",
"bib": "bîssayî",
"zad": "zapotekiya cajonos",
"waz": "wampurî",
"tll": "tetelayî",
"pur": "puruborayî",
"nih": "nyihayî",
"tnk": "kwamerayî",
"ilv": "îlueyî",
"dmn-dam": "damayî (Sierra leone)",
"cba-ata": "atanquesî",
"abu": "abureyî",
"zpt": "zapotekiya san vicente coatlan",
"aaw": "solongyî",
"opy": "ofayeyî",
"dtb": "labuk-kinabatangan kadazanî",
"ikt": "înuvialuktunî",
"auu": "auye",
"adi": "adî",
"wsk": "waskiayî",
"tix": "tiwayiya başûrî",
"atu": "reel",
"mui": "musî",
"iso": "îsokoyî",
"gya": "gbayayiya bakur-rojava",
"auw": "awyî",
"zik": "zimakanî",
"sai-way": "wayumarayî",
"sue": "suenayî",
"sai-opo": "opónî",
"srf": "nafî",
"qxs": "kiangiya başûrî",
"ggt": "gituayî",
"bcd": "babariya bakur",
"xam": "ǀxamî",
"wiu": "wiruyî",
"rro": "roroyî",
"osi": "osingî",
"mvb": "mattolî",
"mjk": "matukarî",
"lln": "leleyî (Çad)",
"kiy": "kirikiriyî",
"sit-jap": "japhugî",
"tds": "doutaî",
"blf": "buol",
"akt": "akolet",
"apb": "sa'a",
"awd-mpr": "maypureyî",
"jee": "jerungî",
"saj": "sahuyî",
"pkn": "pakanhayî",
"kkx": "kohinî",
"nai-cal": "calusayî",
"bgf": "banganduyî",
"ybm": "yabenî",
"bhq": "tukang besî başûr",
"mxe": "mele-filayî",
"lom": "loomayî",
"hka": "kaheyî",
"emn": "emanî",
"boi": "barbarenyoyî",
"aie": "amarayî",
"anh": "nendî",
"mej": "meyahî",
"kcl": "kalayî",
"hro": "haroiyî",
"enc": "enî",
"amy": "amî",
"yde": "yangum deyî",
"taj": "tamangiya rojhilatî",
"sar": "saravekayî",
"szb": "ngalumî",
"inm": "minaeanî",
"kwh": "kowyayî",
"hed": "herdeyî",
"cuy": "cuitlatecî",
"yvt": "yaviteroyî",
"sgw": "sebat bet gurageyî",
"nph": "phomî",
"mjs": "mişipî",
"mda": "madayî (Nîjerya)",
"wat": "kaninuwayî",
"blo": "anîî",
"yss": "yessan-mayoyî",
"ona": "selk'namî",
"pny": "pinyinî",
"noe": "nimadî",
"crm": "kriya mûsî",
"hhy": "hoyahoyayî",
"bzz": "evantî",
"inc-apa": "apabhramsayî",
"uuu": "uyî",
"seu": "serui-lautî",
"pbi": "podokoyî",
"mhc": "moçoyî",
"kzw": "kaririyî",
"faa": "fasuyî",
"sai-chi": "çirinoyî",
"yab": "yuhupî",
"vit": "vitî",
"myl": "momayî",
"crb": "island caribî",
"frq": "forakî",
"cog": "çongî",
"brk": "birgidî",
"waq": "wagemanî",
"nuy": "nunggubuyuyî",
"mdb": "morigî",
"idu": "îdomayî",
"fun": "fulniôî",
"dao": "daai çinî",
"wnb": "wanambreyî",
"skv": "skouyî",
"sai-pur": "purukotó",
"nyi": "nyimangî",
"kwn": "kwangaliyî",
"kec": "keigayî",
"gbu": "gagaduyî",
"dnj": "danî",
"bus": "bokobaruyî",
"ado": "abuyî",
"zpn": "zapotekiya santa ines yatzeçi",
"aml": "war-jaîntî",
"mij": "mungbamî",
"lbu": "labuyî",
"ken": "kenyangî",
"ipo": "îpikoyî",
"zam": "zapotekoyiya mahuatlana navendî",
"wac": "wasko-wişramî",
"sww": "sowayî",
"msu": "musomî",
"rkm": "markayî",
"slp": "lamaholotî",
"gso": "gbayayiya başûr-rojava",
"ahb": "axamb",
"kit": "agobî",
"bfi": "zimanê destan ê brîtanî",
"taz": "toçoyî",
"swi": "suî",
"prm": "kibirî",
"gis": "gizigayiya bakur",
"doe": "doeyî",
"abj": "aka-beayî",
"hnh": "ǁaniyî",
"roo": "rotokasî",
"nlv": "nahwatliya orizaba",
"npl": "nahwatliya pueblaya başûr-rojhilatî",
"llc": "leleyî (Gîne)",
"fkk": "kirya-konzelî",
"mde": "bura mabangî",
"bdl": "bajwayiya endonezyayî",
"bss": "akooseyî",
"scw": "şayî",
"lsm": "saamiayî",
"pqa": "pa'ayî",
"nmq": "nambyayî",
"mbq": "maysînî",
"jib": "jibuyî",
"bhs": "buwalî",
"sjo": "xibeyî",
"abw": "pal",
"nxa": "naueteyî",
"mck": "mbundayî",
"jmx": "juxtlahuaca mixteciya rojavayî",
"clu": "caluyanunî",
"ybo": "yabongî",
"tuo": "tukanoyî",
"nml": "ndemlî",
"mfx": "meloyî",
"lop": "lopayî",
"kzv": "komyandaretî",
"kct": "kaianî",
"has": "haislayî",
"mmw": "emayî",
"cyb": "cayubabayî",
"bcj": "bardî",
"awd-ama": "amarizanayî",
"ylr": "yalarnngayî",
"ago": "taînaeyî",
"prw": "parawenî",
"enw": "enwangî",
"kog": "coguiyî",
"bst": "basketoyî",
"ymo": "yangum monî",
"uda": "udayî",
"tma": "tamayî (Çad)",
"sre": "sarayî",
"nbv": "ngamamboyî",
"mcv": "minanibaî",
"koh": "koyoyî",
"zbt": "batuî",
"anb": "andoa",
"yaw": "yawalapitî",
"tnc": "tanimuca-retuarã",
"sbx": "seberuangî",
"yhl": "hlepho phowayî",
"bzv": "bebeyî",
"anf": "anîmere",
"yet": "yetfayî",
"pir": "piratapuyoyî",
"nmw": "nimoayî",
"nhb": "bengî",
"tbo": "tawalayî",
"szd": "seruyî",
"poy": "pogoloyî",
"bve": "malayiya berau",
"ctm": "çitimaçayî",
"aaf": "aranadanî",
"mkh-mvi": "viyetnamiya navîn",
"mnd": "mondeyî",
"mup": "malvî",
"kse": "kuniyî",
"btf": "birgîtî",
"aac": "ariyî",
"tex": "tennetî",
"kbb": "kaxuyanayî",
"ula": "fungwayî",
"dsh": "daasanaçî",
"zps": "zapotekiya coatlan",
"wnp": "wanapî",
"scs": "slaveyiya bakur",
"lid": "nyindrouyî",
"kvi": "kwangî",
"idb": "îndo-portegîzî",
"bdt": "bokotoyî",
"ate": "atemble",
"zpi": "zapotekiya santa maria quiegolani",
"tvy": "timor pidginî",
"sol": "solosî",
"rjs": "rajbanşî",
"ogc": "ogbahî",
"mth": "mungguî",
"dmc": "gavakî",
"zaj": "zaramoyî",
"sss": "soyî",
"nai-okw": "okwanuçuyî",
"hia": "lamangî",
"gyg": "gbayiyî",
"duy": "dicamay agtayî",
"sgt": "brokpakeyî",
"yix": "axi yî",
"tcx": "todayî",
"sui": "sukî",
"phi-nag": "nagtipunan agtayî",
"ixc": "îxcatecî",
"giz": "gizigayiya başûr",
"baa": "babatanayî",
"akm": "aka-bo",
"wiy": "wiyotî",
"tnt": "tontemboanî",
"uta": "otankî",
"nhc": "nahwatliya tabasco",
"mph": "maungî",
"led": "lenduyî",
"kiz": "kisiyî",
"dva": "duauyî",
"bvr": "burarrayî",
"yig": "wusa nasuyî",
"neb": "tourayî",
"shv": "şehrî",
"nnm": "namiayî",
"nai-wji": "jicaqueyiya rojavayî",
"gve": "duwetî",
"yln": "buyangiya langnî",
"blq": "baluan-pam",
"jiv": "şuarî",
"quv": "sakapultekoyî",
"ynk": "naukanskî",
"mgy": "mbungayî",
"kpx": "koiariyiya çiyayî",
"ktz": "juǀ'hoanî",
"ofu": "efutopî",
"wes": "cameroon pidginî",
"dba": "bangi meyî",
"aab": "alumu-tesuyî",
"bkj": "pande",
"ndg": "ndengerekoyî",
"bbo": "konabereyî",
"zka": "kaimbulawayî",
"hgm": "haiǁomî",
"alv-ama": "amasî",
"ttm": "tutçoneyiya bakurî",
"nen": "nengoneyî",
"zmf": "mfinuyî",
"ruf": "luguruyî",
"kfq": "korkuyî",
"tmd": "haruaî",
"eot": "eotileyî",
"sem-amm": "ammoniteyî",
"tdk": "tambasî",
"sba": "ngambayî",
"lue": "luvaleyî",
"khq": "koyra çiiniyî",
"doc": "kamiya bakurî",
"sai-cul": "cullî",
"ano": "andoque",
"yww": "yawarawargayî",
"uji": "tanjijilî",
"ypz": "phuzayî",
"nsq": "miwokiya sierraya bakurî",
"sgb": "mag-ançî aytayî",
"ish": "esanî",
"aoi": "anîndîlyakwa",
"uri": "urimî",
"yer": "tarokî",
"shb": "ninamî",
"kpj": "karajayî",
"hoz": "hozoyî",
"twm": "tawang monpayî",
"mqo": "modoleyî",
"meo": "malayiya kedah",
"bbv": "karnaî",
"agl": "fembeyî",
"ctg": "çittagonianî",
"tkq": "teeyî",
"wmd": "mamaindeyî",
"kjn": "kunjenî",
"kmg": "kâteyî",
"bqq": "biritaiyî",
"aai": "arîfama-mînîafîayî",
"swj": "sirayî",
"puw": "puluwatî",
"rin": "nunguyî",
"bdo": "moromî",
"khj": "kuturmiyî",
"fvr": "furî",
"dbq": "dabayî",
"blr": "blang",
"mfi": "wandalayî",
"xsl": "slaveyiya başûr",
"pww": "pwoyiya bakurî",
"knp": "kwanjayî",
"gbi": "galelayî",
"bqc": "bokoyî",
"zpr": "zapotekiya santiago xanica",
"sai-tim": "timoteyî",
"rki": "rakhineyî",
"ogb": "ogbiayî",
"mpv": "mungkipî",
"ker": "kerayî",
"iqu": "îquitoyî",
"gou": "gavarî",
"bvf": "boorî",
"tmb": "avavayî",
"was": "waşoyî",
"tkp": "tîkopyayî",
"reb": "rembongî",
"ogo": "khanayî",
"diu": "gcirikuyî",
"orw": "oro wînî",
"nlx": "nahalî",
"vme": "maselayiya rojhilat",
"ltn": "latundê",
"bvx": "baboleyî",
"aci": "aka-karî",
"rug": "rovyanayî",
"kqs": "kissiya bakurî",
"dmu": "dubuyî",
"bwr": "burayî",
"bkc": "baka",
"akf": "akpayî",
"wut": "wutungî",
"she": "şekoyî",
"pma": "paamayî",
"mgi": "jilî",
"gvl": "gulayî",
"dyy": "dyaabugayî",
"aiy": "aliyî",
"xin": "xincayî",
"skf": "sakirabiayî",
"zmp": "mbuunî",
"bex": "jûr modoyî",
"mea": "menkayî",
"kfc": "konda-dorayî",
"kbw": "kaîepî",
"cto": "embera-catioyî",
"cam": "cemuhî",
"bae": "baré",
"tce": "tutçoneyiya başûrî",
"nfu": "mfumteyî",
"lcq": "luhuyî",
"oco": "korniya kevn",
"ktg": "kalkatunguyî",
"sja": "epenayî",
"col": "columbia-wenatçiyî",
"alx": "amol",
"wca": "yanomamî",
"xxt": "tamborayî",
"auk": "heyo",
"cuo": "cumanagotoyî",
"sai-and": "andoqueroyî",
"yva": "yawayî",
"due": "umiray dumaget agtayî",
"kml": "tanudan kalingayî",
"ptr": "piamatsinayî",
"blx": "mag-îndî ayta",
"gcl": "kreyoliya îngilîzî ya grenadî",
"ese": "ese ejjayî",
"uvh": "urî",
"nsm": "semayî",
"nin": "ninzoyî",
"hra": "hrangkholî",
"nai-taw": "tawasayî",
"sbi": "setî",
"mvq": "moereyî",
"mqg": "malayiya kota bangun kutai",
"uwa": "kuku-uwanhî",
"iow": "çiwereyî",
"bid": "bidiyoyî",
"yuk": "yukî",
"teh": "tehuelçeyî",
"mkc": "siliputî",
"zle-ono": "novgorodiya kevn",
"wmh": "waima'ayî",
"tio": "teopî",
"yae": "pumeyî",
"mqn": "moroneneyî",
"zma": "mandayî (Awistralya)",
"kdc": "kutuyî",
"kyt": "kayagarî",
"mps": "dadibî",
"byn": "blinî",
"cns": "asmatiya navendî",
"utp": "abayî",
"zpc": "zapotekiya çoapan",
"den": "slaveyî",
"pwo": "pwoyiya rojavayî",
"mub": "mubî",
"mev": "manoyî",
"bij": "kwankayî",
"kyo": "kelonî",
"inb": "îngayî",
"aph": "athpare",
"zas": "zapotekiya santo domingo albarradas",
"ral": "ralteyî",
"irn": "îrantxeyî",
"dge": "degenanî",
"boa": "borayî",
"adj": "adyukruyî",
"zal": "zauzouyî",
"wax": "watamî",
"tca": "tîkunayî",
"agv": "remontado agta",
"omg": "omaguayî",
"xru": "marriammuyî",
"khn": "khandeşiyî",
"gji": "gejiyî",
"ayd": "ayabadhu",
"zmb": "zimbayî",
"toj": "tojolabalî",
"syk": "sukurî",
"bnf": "masiwang",
"glw": "glavdayî",
"bav": "babungoyî",
"akx": "aka-kede",
"ots": "otomiya estado de meksîka",
"mjj": "mawakî",
"leh": "lenjeyî",
"kss": "kissiyiya başûrî",
"kbz": "duhwayî",
"sth": "şeltayî",
"myf": "bambassî",
"ald": "alladînî",
"ego": "eggonî",
"cbu": "candoşi-şaprayî",
"tmc": "tumakî",
"pgs": "pangsengî",
"ndz": "ndogoyî",
"knd": "kondayî",
"haj": "hajongî",
"cmi": "embera-çamiyî",
"mlp": "bargamî",
"yat": "yambetayî",
"bsg": "başkardiyî",
"pek": "pençalî",
"lwo": "luwoyî",
"kpk": "kpanî",
"kdx": "kamî",
"cug": "cungî",
"anx": "andra-hus",
"sey": "secoyayî",
"pio": "piapocoyî",
"jgo": "ngombayî",
"miy": "mixteciya ayutla",
"gyn": "kreyoliya îngilîzî ya guyanî",
"hoa": "hoavayî",
"amx": "anmatyerre",
"tdm": "tarumayî",
"sza": "semelayî",
"pit": "pitta-pittayî",
"nsz": "nisenanî",
"mis-mmd": "mîmiya decorseyî",
"mky": "makianiya rojhilat",
"kdj": "karamojongî",
"hre": "hreyî",
"bie": "bepourî",
"ena": "apaliyî",
"vgr": "vaghrî",
"sze": "sezeyî",
"not": "nomatsiguengayî",
"mif": "mofu-gudurî",
"mfp": "malayiya makassar",
"kul": "kulereyî",
"huo": "huyî",
"biu": "biete",
"twe": "teiwayî",
"stv": "silt'eyî",
"mqj": "mamasayî",
"kug": "kupayî",
"chw": "çuaboyî",
"zkr": "zakhringî",
"waw": "waiwaî",
"ntp": "tepehuaniya bakurî",
"pmi": "pumiya bakurî",
"hux": "nüpode huitotoyî",
"mor": "moroyî",
"mha": "mandayî (Hindistan)",
"thd": "kuuk thaayorreyî",
"dgz": "dagayî",
"mae": "bo-rukulî",
"sjg": "assangorî",
"zpg": "zapotekiya guevea de humboldt",
"tcz": "thado çinî",
"nyv": "nyulnyulî",
"aso": "dano",
"bld": "bolango",
"snk": "soninkeyî",
"sai-mar": "maratinoyî",
"laf": "lafofayî",
"krk": "kerekî",
"gyl": "gayilî",
"ksr": "borongî",
"avi": "avîkam",
"wah": "watubelayî",
"tpl": "tlacoapa me'phaayî",
"mfr": "marrithiyelî",
"khr": "khariayî",
"giq": "gelaoyiya hêşîn",
"aye": "ayere",
"owl": "weylsiya kevn",
"zsu": "sukurumî",
"nhk": "nahwatliya cosoleacaque",
"law": "laujeyî",
"izr": "îzereyî",
"goi": "gobasiyî",
"wog": "wogamusinî",
"ttu": "torauyî",
"dts": "toro so dogonî",
"mcw": "mawayî",
"azc-ktn": "kitanemukî",
"ddj": "jaruyî",
"dma": "dumayî",
"bkh": "bakoko",
"aqz": "akuntsu",
"pdo": "padoeyî",
"nnb": "nandeyî",
"dzl": "dzalayî",
"bax": "bamumî",
"xiy": "xipayayî",
"tct": "t'enî",
"lht": "lo-togayî",
"kkk": "kokotayî",
"lot": "lotukoyî",
"tdc": "embera-tadó",
"bjc": "barijî",
"aew": "ambakîç",
"tkm": "takelmayî",
"sbp": "sanguyî (Tanzanya)",
"sai-ptg": "patagónî",
"lgk": "neververî",
"ybj": "haşayî",
"amg": "amurdag",
"yns": "yansî",
"udu": "udukî",
"mwm": "sarî",
"ums": "pendauyî",
"wii": "minidienî",
"pbn": "kpasamî",
"cuq": "cunî",
"bbd": "bauyî",
"aty": "aneityum",
"ngc": "ngombeyî (Kongo)",
"kxo": "kanoeyî",
"ags": "esîmbî",
"bxb": "belanda borî",
"urn": "uruangnirinî",
"mis-mmn": "mîmiya naçtigalî",
"hru": "hrusoyî",
"apt": "apatanî",
"yon": "yongkomî",
"vag": "vaglayî",
"vkl": "kulisusuyî",
"kxh": "karoyî",
"jab": "hyamî",
"smg": "simbalî",
"asj": "nsarî",
"knk": "kurankoyî",
"kaf": "katsoyî",
"yml": "îamaleleyî",
"brt": "bitareyî",
"bao": "waîmaha",
"tla": "tepehuaniya başûr-rojavayî",
"pmj": "pumiya başûrî",
"nuf": "nusuyî",
"mrl": "mortlockeseyî",
"man": "mandingoyî",
"ign": "îgnacianoyî",
"bzn": "boanoyî (Maluku)",
"xss": "assanî",
"wne": "wanecî",
"thf": "thangmî",
"kjb": "q'anjob'alî",
"sai-muc": "mucuçî",
"msw": "mansoankayî",
"kem": "kemakî",
"bnp": "bolayî",
"aqm": "atohwaim",
"zat": "zapotekiya tabaa",
"sov": "sonsorolî",
"lhn": "lahananî",
"kjq": "keresiya rojavayî",
"dei": "demisayî",
"kwi": "awa-cuaiquerî",
"aez": "aekayî",
"zay": "zayse-zergullayî",
"una": "watutiya bakur",
"ctl": "tlacoatzintepec çinantecî",
"squ": "squamîşî",
"omb": "ombayî",
"sur": "mwaghavulî",
"umc": "marrusînî",
"lbn": "lametî",
"phi-din": "dinapigue agtayî",
"buw": "bubiyî",
"ahn": "ahàn",
"zrn": "zirenkelî",
"srg": "sulodî",
"ora": "orohayî",
"mtn": "matagalpayî",
"kco": "kinalaknayî",
"hmd": "a-hmaoyî",
"woc": "wogeoyî",
"tqr": "toronayî",
"rbb": "rumaî palaungî",
"ott": "otomiya temoaya",
"nhi": "nahwatliya zacatlan-ahuacatlan-tepetzintla",
"mgk": "mawesî",
"dus": "dumiyî",
"bzu": "burmesoyî",
"bbu": "bakulungî",
"trn": "trinitarioyî",
"sdp": "şerdukpenî",
"sbv": "sabineyî",
"sai-jrj": "jirajirayî",
"gvs": "gumawanayî",
"dnn": "dzuunî",
"tte": "bwanabwanayî",
"bcf": "bamuyî",
"xir": "xiriânayî",
"two": "tswapongî",
"sai-tab": "tabancaleyî",
"lgq": "logbayî",
"klm": "kolomî",
"ngs": "gvokoyî",
"eka": "ekajukî",
"dbw": "bankan tey dogonî",
"aly": "alyawarr",
"yad": "yaguayî",
"ndp": "ndoyî",
"mcr": "menyayî",
"xon": "konkombayî",
"kka": "kakandayî",
"hao": "haköyî",
"ckl": "cibakî",
"jbk": "barikewayî",
"abc": "ambala ayta",
"yaa": "yaminahuayî",
"sny": "saniyo-hiyeweyî",
"mmc": "miçoacan mazahuayî",
"xkq": "koronî",
"had": "hatamî",
"sai-cop": "copallenî",
"swy": "saruayî",
"nid": "ngandî",
"mhi": "ma'dî",
"cpo": "kpeeyî",
"anz": "anem",
"unz": "unde kailî",
"sai-tpr": "taparitayî",
"sed": "sedangî",
"fpe": "piçinglisî",
"mxy": "mixteciya noçixtlana başûr-rojhilatî",
"bfz": "mahasu paharî",
"sai-esm": "esmeraldayî",
"urv": "uruavayî",
"seh": "senayî",
"spx": "pîkeneyiya başûr",
"cag": "nivacleyî",
"zkk": "karankawayî",
"bkl": "berik",
"yut": "yopnoyî",
"vai": "vaî",
"slw": "sialumî",
"noj": "nonuyayî",
"caf": "karrieriya başûrî",
"fag": "finonganî",
"ibh": "bihî",
"awt": "araweté",
"tyx": "teke-tyeeyî",
"mog": "mongondowî",
"knt": "katukinayî",
"bwm": "biwatî",
"apr": "arop-lokep",
"atr": "waîmîrî-atroarî",
"mtt": "motayî",
"pym": "fyamî",
"dlk": "dahalikî",
"bzl": "boanoyî (sulawesî)",
"abp": "abenlen ayta",
"zaw": "zapotekiya mitla",
"svm": "slavomolisanoyî",
"zpz": "zapotekiya texmelucan",
"tit": "tiniguayî",
"rax": "rangî",
"asx": "muratayak",
"mvr": "marauyî",
"lha": "lahayî (Viyetnam)",
"irh": "îrarûtûyî",
"sai-gay": "gayónî",
"awi": "aekyom",
"mcy": "watutiya başûr",
"tpt": "tlaçiçilco tepehuayî",
"sto": "stoneyî",
"rea": "rerauyî",
"aom": "ömie",
"tlx": "khehekî",
"mrx": "dineorî",
"oro": "orokoloyî",
"acl": "akar-baleyî",
"wbw": "woî",
"otn": "otomiya tenango",
"gia": "kitjayî",
"ats": "gros ventre",
"tri": "trió",
"zmz": "mbandjayî",
"yzg": "e'ma buyangî",
"bwa": "bwatûyî",
"bcw": "banayî",
"ywt": "xişanba laloyî",
"tsc": "tswayî",
"gmw-tsx": "saksoniya transylvanyayê",
"fap": "palorî",
"nwa": "nawathinehenayî",
"mue": "media lenguayî",
"kcx": "kaçama-ganjuleyî",
"dah": "gwahatikeyî",
"ekg": "ekariyî",
"cbc": "carapanayî",
"bxw": "bankayî",
"ygw": "yagwoiayî",
"tbr": "tumtumî",
"tgw": "tagwana senoufoyî",
"pnr": "panimî",
"njt": "ndyuka-trio pidginî",
"vnk": "lovonoyî",
"kma": "konniyî",
"hal": "halangî",
"awc": "cicipu",
"bch": "bariyayî",
"gfk": "patpatarî",
"ule": "luleyî",
"vkm": "kamakanî",
"xht": "hattîkî",
"btz": "batakiya alas-kluet",
"dti": "ana tinga dogonî",
"yre": "yaoureyî",
"udj": "ujirî",
"tpu": "tampuanî",
"ung": "ngarinyinî",
"xjb": "minjungbalî",
"slz": "ma'yayî",
"kpe": "kpelleyî",
"xla": "kamulayî",
"kpc": "curripacoyî",
"bvz": "bawziyî",
"ets": "yekheeyî",
"bbn": "uneapayî",
"tvw": "sedoayî",
"crp-spp": "pidginiya samoan plantation",
"ngq": "ngoremeyî",
"mil": "mixteciya peñoles",
"sai-chn": "çanayî",
"kvu": "yinbaw karenî",
"txx": "tatanayî",
"lva": "maku'ayî",
"gel": "fakkanciyî",
"bom": "beromî",
"cvn": "valle nacional çinantecî",
"sya": "siangî",
"bql": "bilakurayî",
"arv": "arbore",
"sai-yup": "yupuayî",
"vnp": "vunapuyî",
"sbb": "simboyî",
"gmw-pro": "proto-cermeniya rojava",
"nbn": "kurî",
"wbv": "wajarrî",
"mwh": "moûk-arîakî",
"bwp": "mandobo bawahî",
"fie": "fyerî",
"dal": "dahaloyî",
"aba": "abeyî",
"zpm": "zapotekiya mixtepec",
"wky": "wangkayutyuruyî",
"laq": "qabiaoyî",
"nja": "nzanyî",
"dmo": "kemezungî",
"dro": "daro-matu melanauyî",
"ato": "atong (Kamerun)",
"yif": "açeyî",
"zts": "zapotekiya tilquiapan",
"lah": "lahndayî",
"iry": "îrayayî",
"roa-bbn": "bourbonnais-berriçonî",
"aal": "afadeyî",
"czn": "zenzontepekiya çatîno",
"mpl": "watutiya navîn",
"lmg": "lamogayî",
"nlg": "gelayî",
"ayo": "ayoreoyî",
"zoh": "zoqueyiya çimalapa",
"mtp": "wiçi lhamtes noctenî",
"suv": "sulungî",
"xmt": "matbatî",
"zga": "kingayî",
"jbt": "jabutiyî",
"gof": "gofayî",
"rmt": "domarî",
"bwu": "bulî (Gana)",
"jod": "wojenakayî",
"yyu": "torricelli yauyî",
"sbf": "şaboyî",
"yan": "mayangnayî",
"kii": "kitsaiyî",
"trc": "trîkuyiya copala",
"shx": "şeyî",
"pgu": "paguyî",
"gwa": "mbatoyî",
"dyo": "jola-fonyiyî",
"yas": "gunuyî",
"igb": "ebirayî",
"plz": "paluanî",
"alv-gwa": "gwarayî",
"eke": "ekitî",
"cfd": "carayî",
"abb": "bankonî",
"amc": "amahuaca",
"rhp": "yahangî",
"lmb": "mereiyî",
"koa": "konomalayî",
"hlb": "halbiyî",
"eme": "emerillonî",
"nai-cig": "ciguayoyî",
"ael": "ambeleyî",
"jay": "yan-nhanguyî",
"tak": "talayî",
"mmy": "migaamayî",
"lmd": "lumunî",
"kfp": "korwayî",
"woi": "kamangî",
"akg": "anakalunguyî",
"nai-per": "pericú",
"nrl": "ngarlumayî",
"mew": "maakayî",
"hig": "kamweyî",
"hnd": "hindkoyiya başûrî",
"cuv": "cuvokî",
"ylg": "yeloguyî",
"une": "unemeyî",
"tpj": "tapieteyî",
"sos": "seenkuyî",
"nsh": "ngoşieyî",
"mit": "mixteciya pueblaya başûr",
"mxx": "mahouyî",
"bzk": "kreyoliya nîkaraguayî",
"esq": "esselenî",
"nbc": "çangî",
"anu": "anuak",
"snu": "senggî",
"mpu": "makurapî",
"hbb": "hubayî",
"cbg": "çimilayî",
"wti": "bertayî",
"app": "apma",
"spg": "sianî",
"snf": "noonî",
"mkp": "moikodî",
"viv": "idunayî",
"gem-pro": "proto-cermenî",
"omx": "moniya kevn",
"maw": "mamprulî",
"are": "arrernteya rojavayî",
"zah": "zangwalî",
"ssl": "sisaalayiya rojavayî",
"mpc": "mangarayî",
"qun": "kuynaultî",
"gyd": "kayardildî",
"ikx": "îkî",
"dij": "daiyî",
"bwq": "boboyiya başûrî",
"aum": "asu",
"axb": "abipon",
"zac": "zapotekiya ocotlan",
"xwk": "wangkumarayî",
"nzi": "nzimayî",
"mzv": "manzayî",
"kwe": "kwerbayî",
"sjk": "kemi samî",
"gmv": "gamoyî",
"bkt": "bolokî",
"atw": "atsugewî",
"zpk": "zapotekiya tlacolulita",
"wbe": "waritaî",
"ray": "rapayî",
"ojs": "ojibweyiya severnê",
"zmd": "maridanî",
"kxz": "kerewoyî",
"irr": "îrî",
"gbr": "gbagyiyî",
"bzx": "bozoyiya hainyaxo",
"aya": "awar",
"res": "reşeyî",
"oin": "oneyiya inebu",
"mwe": "mwerayî",
"ite": "îteneyî",
"gaf": "gendeyî",
"gdl": "diraşayî",
"nfd": "ahwayî",
"tlm": "tolomakoyî",
"mgv": "matengoyî",
"gqn": "kinikinaoyî",
"jah": "jah hutî",
"gog": "gogoyî",
"doy": "dompoyî",
"bfj": "bafanjî",
"xwc": "wokkonî",
"tei": "torricellî",
"sha": "şall-zwallî",
"stn": "owayî",
"emy": "mayaniya epigrafîk",
"azd": "durango nahuatliya rojavayî",
"bls": "balaesang",
"dul": "alabat island agtayî",
"xan": "xamtangayî",
"plk": "şinayiya kohistanî",
"sai-sac": "sacatayî",
"apf": "pahanan agta",
"npa": "nar phuyî",
"mfn": "mbembeyiya çema crossê",
"czk": "knaanicî",
"jow": "jowuluyî",
"cbb": "cabiyariyî",
"bpq": "banda malayî",
"sit-tsh": "tşobdunî",
"sbe": "salibayî (Gîneya nû)",
"mfj": "mefeleyî",
"lok": "lokoyî",
"kdu": "kadaruyî",
"ekp": "ekpeyeyî",
"bap": "bantawa",
"ali": "amaimonî",
"ynu": "yahunayî",
"tuf": "tuneboyiya navendî",
"ppu": "paporayî",
"dcr": "negerhollandsî",
"meq": "mereyî",
"nai-knm": "konomihuyî",
"ijn": "kalabariyî",
"hla": "haliayî",
"alm": "amblongî",
"ynn": "yanayî",
"ntj": "ngaanyatjarrayî",
"kbq": "kamanoyî",
"anm": "analî",
"ukg": "ukurigumayî",
"mav": "satere-maweyî",
"mfz": "mabaanî",
"kqq": "krenakî",
"hns": "hindustaniyiya karibî",
"taf": "tapirapeyî",
"mig": "mixteciya san miguel el grande",
"kni": "kanufiyî",
"sai-chp": "çapacurayî",
"cko": "anufoyî",
"wnu": "usanî",
"spe": "sepayî (Gîneya nû)",
"xrw": "karawayî",
"bnt-cmw": "çimwiinî",
"vam": "vanimoyî",
"sjr": "siar-lakî",
"poo": "pomoyiya navendî",
"mlz": "malaynonî",
"xks": "kumbewahayî",
"ldb": "idunî",
"tqb": "tembeyî",
"nai-sin": "sinacantanî",
"nur": "nuguriayî",
"mqx": "mamujuyî",
"unn": "kurnayî",
"bcn": "bîbaalî",
"ttr": "terayî",
"wkd": "moyî",
"kzl": "kayeliyî",
"ntk": "îkomayî",
"gdg": "ga'dangî",
"gbj": "bodo gadabayî",
"abi": "abîdjî",
"zao": "zapotekiya ozolotepec",
"sai-wnm": "wanhamî",
"thp": "thompsonî",
"cwe": "kwereyî",
"xbn": "kenaboî",
"gnq": "ganayî",
"tcc": "datoogayî",
"ztg": "zapotekiya xanaguia",
"zmj": "maridjabinî",
"lak": "lakayî",
"kvr": "kerînçî",
"ggu": "gbanî",
"anv": "denya",
"aof": "bragat",
"awr": "awera",
"zhb": "zhabayî",
"www": "wawayî",
"stb": "subaneniya bakurî",
"ret": "rettayî",
"oon": "öngeyî",
"mlv": "mwotlapî",
"mzr": "marúboyî",
"lag": "langiyî",
"kxc": "khonsoyî",
"itx": "îtikî",
"bja": "budza",
"ayu": "ayuyî",
"tba": "aikanayî",
"txm": "tominî",
"orx": "oroyî",
"nhq": "nahwatliya huaxcaleca",
"mpq": "matisî",
"kfk": "kinnauriyî",
"kzh": "dongolawiyî",
"bck": "bunabayî",
"ksf": "bafiayî"
},
"numberLabels": {
"y": {
"display": "yekjimar"
},
"d": {
"cat": "dualia tantum",
"display": "hejmar dual"
},
"p": {
"cat": "POS pirjimar",
"display": "pirjimar"
},
"p+": {
"cat": "POS axlebî pirjimar",
"display": "axlebî pirjimar"
},
"tp": {
"cat": "POS tenê pirjimar",
"display": "tenê pirjimar"
},
"j": {
"cat": "POS jimarbar",
"display": "jimarbar"
},
"j+": {
"cat": "POS axlebî jimarbar",
"display": "axlebî jimarbar"
},
"j/nj": {
"cat": "POS jimarbar û nejimarbar",
"display": "jimarbar û nejimarbar"
},
"nj": {
"cat": "POS nejimarbar",
"display": "nejimarbar"
},
"nj+": {
"cat": "POS axlebî nejimarbar",
"display": "axlebî nejimarbar"
},
"hj!": {
"display": "hejmar nepeyitandî ye"
},
"hj?": {
"req": true,
"display": "hejmar hewce ye"
}
}
}
62jnda01elmfyftcs7l937207s9x1h1
6334371
6334365
2026-08-17T17:45:29Z
Wikihez
7344
6334371
json
application/json
{
"langChoices": {
"ku": "kurmancî",
"zza": "zazakî",
"ckb": "soranî",
"sdh": "kurdiya başûrî",
"hac": "hewramî",
"de": "almanî",
"ar": "erebî",
"fa": "farisî",
"fr": "fransî",
"fi": "fînî",
"en": "îngilîzî",
"ja": "japonî",
"sv": "swêdî",
"tr": "tirkî"
},
"posChoices": {
"navdêr": "Navdêr (îsm)",
"lêker": "Lêker (fiîl)",
"rengdêr": "Rengdêr (sifet)",
"hoker": "Hoker (zerf)",
"serenav": "Serenav (îsmê xisûsî)",
"artîkel": "Artîkel",
"baneşan": "Baneşan (nîda)",
"bazinedaçek": "Bazinedaçek",
"cînav": "Cînav (zemîr)",
"daçek": "Daçek (edat)",
"girêdek": "Girêdek",
"gotineke pêşiyan": "Gotineke pêşiyan",
"hejmar": "Hejmar",
"hevok": "Hevok (cimle)",
"îfade": "Îfade",
"kurtenav": "Kurtenav",
"navgir": "Navgir",
"paşdaçek": "Paşdaçek",
"paşgir": "Paşgir",
"pêşdaçek": "Pêşdaçek",
"pêşgir": "Pêşgir",
"pirtik": "Pirtik",
"reh": "Reh",
"sembol": "Sembol",
"tîp": "Tîp (herf)"
},
"posExamples": {
"navdêr": "Kelîmeyên ji bo tiştan (sêv, heywan, qedeh) yan terman (dilşadî, bextiyarî hwd.) di rêzimanê de.",
"lêker": "Kelîmeyên ji bo kirin an bûyînê wekî 'nivîsîn' yan 'xeber dan'.",
"rengdêr": "Kelîmeyên ku wesfa navdêran nîşan didin wekî 'sêva sor' yan 'xwarina şîrîn'.",
"hoker": "Kelîmeyên ku dem, cih yan awayê kirinê diyar dikin wekî 'bi vî awayî' yan 'demkî'.",
"daçek": "Kelîmeyên ku cih, dem yan hereketê diyar dikin wekî 'di.. de' yan 'ji'.",
"cînav": "Kelîmeyên ku li şûna navdêran tên bikaranîn daku navdêr neyên dubarekirin wekî 'ew' yan 'em'.",
"baneşan": "Kelîmeyên ku hest, deng hwd. îfade dikin wekî 'noşî!' yan 'aferîn'.",
"serenav": "Navdêrên ku şexsiyetên ferdî nîşan didin, wek navên kes, cih an organîzasyonan nimûne 'London' yan 'Diyarbekir'.",
"gotineke pêşiyan": "Gotinên ku di nav xelkê de bi berbelavî tên bikaranîn bo nimûne 'Serê neêşe ne hewceyî destmalê ye'."
},
"genderLabels": {
"m": "mê",
"n": "nêr",
"nt": "nêtar",
"mn": "mê yan nêr",
"mnmane": "mê yan nêr li gorî maneyê",
"mndudil": "mê yan nêr bikaranîn dudil e",
"g": "giştî",
"y": "yekjimar",
"p": "pirjimar"
},
"tplDefs": {
"rastnivîs": {
"langs": [
"ku"
],
"noLang": true
},
"ku-Latn": {
"langs": [
"ku"
],
"noLang": true
},
"ku-tewîna-nav": {
"langs": [
"ku"
],
"noLang": true
},
"sdh-Arab": {
"langs": [
"sdh"
],
"noLang": true
},
"hac-Arab": {
"langs": [
"hac"
],
"noLang": true
},
"ja-romanîzekirin": {
"langs": [
"ja"
],
"noLang": true
},
"ckb-Arab": {
"langs": [
"ckb"
],
"noLang": true
},
"zza-awayê-Jacobsonê": {
"langs": [
"zza"
]
},
"awayekî din": {
"langs": null
},
"guharto": {
"langs": null
},
"hevmaneya": {
"langs": null
},
"kevnbûyî": {
"langs": null
},
"kêmpeyda": {
"langs": null
},
"kurtkirin": {
"langs": null
},
"paşnav": {
"langs": null
},
"şaşnivîs": {
"langs": null
}
},
"jederDefs": {
"jêder-kitêb": {
"label": "Kitêb",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"width": "wide"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal",
"multiline": true,
"width": "wide"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cih",
"label": "Cih"
},
{
"key": "sal",
"label": "Sal",
"width": "narrow"
},
{
"key": "çap",
"label": "Çap",
"width": "narrow"
},
{
"key": "rûpel",
"label": "Rûpel",
"width": "narrow"
},
{
"key": "isbn",
"label": "ISBN",
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"width": "wide"
}
]
},
"jêder-kovar": {
"label": "Kovar",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "nivîskar2",
"label": "Nivîskar 2"
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"width": "wide"
},
{
"key": "sernavê orjînal",
"label": "Sernavê orjînal",
"multiline": true,
"width": "wide"
},
{
"key": "kovar",
"label": "Kovar"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "weşanxane",
"label": "Weşanxane"
},
{
"key": "cild",
"label": "Cild",
"width": "narrow"
},
{
"key": "hejmar",
"label": "Hejmar",
"width": "narrow"
},
{
"key": "sal",
"label": "Sal",
"width": "narrow"
},
{
"key": "rûpel",
"label": "Rûpel",
"width": "narrow"
},
{
"key": "url",
"label": "URL",
"width": "wide"
}
]
},
"jêder-websîte": {
"label": "Websîte",
"fields": [
{
"key": "nivîskar",
"label": "Nivîskar"
},
{
"key": "sernav",
"label": "Sernav",
"multiline": true,
"width": "wide"
},
{
"key": "terciman",
"label": "Terciman"
},
{
"key": "websîte",
"label": "Websîte"
},
{
"key": "url",
"label": "URL",
"width": "wide"
},
{
"key": "arşîv",
"label": "Arşîva URLê",
"width": "wide"
},
{
"key": "sal",
"label": "Sal",
"width": "narrow"
},
{
"key": "tarîx",
"label": "Tarîx",
"width": "narrow"
}
]
}
},
"pronTpls": {
"ku-IPA": {
"desc": "IPAya otomatîk ji bo kurmancî (kontrol hewce ye)",
"snippet": "* {{ku-IPA}}",
"langs": [
"ku"
],
"noLang": true
},
"ku-kîte": {
"desc": "Hecekirina otomatîk ji bo kurmancî",
"snippet": "* {{ku-kîte}}",
"langs": [
"ku"
],
"noLang": true
},
"zza-IPA": {
"desc": "IPAya otomatîk ji bo zazakî",
"snippet": "* {{zza-IPA}}",
"langs": [
"zza"
],
"noLang": true
},
"zza-kîte": {
"desc": "Hecekirina otomatîk ji bo zazakî",
"snippet": "* {{zza-kîte}}",
"langs": [
"zza"
],
"noLang": true
},
"IPA": {
"desc": "Alfabeya fonetîk a navmiletî (IPA)",
"snippet": "* {{IPA|CODE|/…/}}"
},
"deng": {
"desc": "Nimûneya telafûzê bi dosyeya dengî",
"snippet": "* {{deng|CODE|navê_dosyayê.ogg|dever=}}"
},
"bilêv": {
"desc": "{{bilêv|herf|nerm, req|hejmar}}",
"snippet": "* {{bilêv|herf|cure|hejmar}}"
},
"kîte": {
"desc": "Şablona hecekirinê",
"snippet": "* {{kîte|CODE|he|ce}}"
},
"hevdeng": {
"desc": "Hevdeng — gotinên bi heman dengî",
"snippet": "* {{hevdeng|CODE|…}}"
}
},
"etymTpls": {
"ji": {
"desc": "Ji zimanekî din hatiye — {{ji|en|ku|anatomy|tr=|t=}}",
"snippet": "{{ji|SOURCELANG|CODE|…}}"
},
"deyn": {
"desc": "Deynkirî — {{deyn|lang|kod|kelîme}}",
"snippet": "{{deyn|SOURCELANG|CODE|…}}"
},
"mîr": {
"desc": "Mîrasmayî — {{mîr|lang|kod|kelîme}}",
"snippet": "{{mîr|SOURCELANG|CODE|…}}"
},
"qertaf": {
"desc": "Ji bi pêşgir, paşgir û hevedudanî — {{qertaf|kod|pêşgir-|reh}}",
"snippet": "{{qertaf|CODE|…|…}}"
},
"q": {
"desc": "Referans bo rûpelekê — {{q|kod|kelîme}} navê zimanî dernakeve",
"snippet": "{{q|CODE|…}}"
},
"hv": {
"desc": "Hevreh — {{hv|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{hv|SOURCELANG|…}}"
},
"nh": {
"desc": "Nehevreh — {{nh|lang|kelîme}} navê zimanî derdikeve",
"snippet": "{{nh|SOURCELANG|…}}"
},
"cotreh": {
"desc": "Cotreh — teror {{cotreh|ku|tirs}}",
"snippet": "{{cotreh|CODE|…}}"
},
"yekser": {
"desc": "tercimeya yekser — {{yekser|lang|kod|kelîme}}",
"snippet": "{{yekser|SOURCELANG|CODE|…}}"
},
"wergirtin": {
"desc": "Wergirtina maneyê — {{wergirtin|lang|kod|kelîme}}",
"snippet": "{{wergirtin|SOURCELANG|CODE|…}}"
},
"paşvesazî": {
"desc": "Paşvesazî — greed {{paşvesazî|en|greedy}}",
"snippet": "{{paşvesazî|CODE|…}}"
},
"qusandin": {
"desc": "Qusandin — Miho {{qusandin|ku|Mihemmed}}",
"snippet": "{{qusandin|CODE|…}}"
},
"têkilkirin": {
"desc": "Têkilkirin — sormancî {{têkilkirin|ku|soranî|kurmancî}}",
"snippet": "{{têkilkirin|CODE|…}}"
},
"nyz": {
"desc": "Nayê zanîn — {{nyz|ku|hûr=1}}",
"snippet": "{{nyz|CODE}}"
},
"onom": {
"desc": "Onomatopoetîk — {{onom|ku|hûr=1}}",
"snippet": "{{onom|CODE}}"
},
"navmiletî": {
"desc": "Navmiletî — {{navmiletî|ku|hûr=1}}",
"snippet": "{{navmiletî|CODE}}"
},
"neo": {
"desc": "neolojîzm — {{neolojîzm|ku|hûr=1}}",
"snippet": "{{neolojîzm|CODE}}"
}
},
"metadata": {
"aa": {
"haswikt": 1,
"sc": [
"Latn",
"Ethi"
]
},
"ab": {
"haswikt": 1,
"sc": [
"Cyrl",
"Latn",
"Geor"
]
},
"aer": {
"sc": "Latn"
},
"af": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ak": {
"haswikt": 1
},
"akk": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Xsux"
},
"als": {
"haswikt": 1
},
"am": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1,
"sc": "Ethi"
},
"an": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ang": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"ar": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"alt": 1,
"haswikt": 1,
"sc": "Arab"
},
"arc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": [
"Hebr",
"Syrc"
]
},
"are": {},
"arz": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"sc": "Arab"
},
"as": {
"haswikt": 1,
"sc": "Beng"
},
"ast": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"av": {
"haswikt": 1
},
"ay": {
"haswikt": 1
},
"az": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl",
"Arab"
]
},
"ba": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"bar": {},
"bat-smg": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"bbl": {
"sc": "Geor"
},
"be": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"be-x-old": {
"sc": "Cyrl"
},
"bg": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"bh": {
"haswikt": 1,
"sc": "Deva"
},
"bhb": {
"sc": "Deva"
},
"bi": {
"haswikt": 1
},
"blt": {
"sc": "Tavt"
},
"bm": {
"haswikt": 1,
"sc": [
"Latn",
"Nkoo",
"Arab"
]
},
"bn": {
"haswikt": 1,
"sc": "Beng"
},
"bo": {
"haswikt": 1,
"sc": "Tibt"
},
"br": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ca": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"cdo": {
"sc": "Hani"
},
"ce": {
"sc": "Cyrl"
},
"ch": {
"haswikt": 1
},
"chr": {
"haswikt": 1,
"sc": "Cher"
},
"cjy": {
"sc": "Hani"
},
"ckb": {
"haswikt": 1,
"sc": "Arab",
"wsc": "ku-Arab"
},
"cmn": {
"sc": "Hani",
"wiktprefix": "zh"
},
"co": {
"haswikt": 1
},
"cpx": {
"sc": "Hani"
},
"cr": {
"haswikt": 1,
"sc": "Cans"
},
"crh": {},
"cs": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"csb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"cu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": [
"Cyrs",
"Glag"
]
},
"cv": {
"sc": "Cyrl"
},
"cy": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"czh": {
"sc": "Hani"
},
"czo": {
"sc": "Hani"
},
"da": {
"g": [
"g",
"nt",
"g-p",
"nt-p",
"p"
],
"haswikt": 1
},
"dax": {},
"de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"dhg": {},
"djb": {},
"dji": {},
"djr": {},
"dng": {
"sc": "Cyrl"
},
"dsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"dsx": {},
"duj": {},
"dv": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Thaa"
},
"dz": {
"haswikt": 1,
"sc": "Tibt"
},
"el": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"en": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"eo": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"es": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"et": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"ett": {
"g": [
"y",
"p"
],
"sc": "Ital"
},
"eu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fa": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-cls": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fa-ira": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"fi": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"fil": {},
"fj": {
"haswikt": 1
},
"fo": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1
},
"fr": {
"g": [
"n",
"m",
"mnmane",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"frm": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fro": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fur": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"fy": {
"g": [
"g",
"nt",
"p"
],
"haswikt": 1
},
"ga": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"gan": {
"sc": "Hani"
},
"gd": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gez": {
"sc": "Ethi"
},
"gl": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"gmw-ecg": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gmy": {
"sc": "Linb"
},
"gn": {
"haswikt": 1
},
"gnn": {
"sc": "Latn"
},
"got": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"sc": "Goth"
},
"grc": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
]
},
"gsw": {
"g": [
"n",
"m",
"nt",
"p"
],
"allowCaps": 1
},
"gu": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Gujr"
},
"guf": {
"sc": "Latn"
},
"gv": {
"haswikt": 1
},
"ha": {
"haswikt": 1
},
"hak": {
"sc": "Hani"
},
"har": {
"sc": "Ethi"
},
"he": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"hi": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Deva"
},
"hif": {
"sc": [
"Latn",
"Deva"
]
},
"hit": {
"sc": "Xsux"
},
"hrx": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"hsb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"hsn": {
"sc": "Hani"
},
"hu": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"hy": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Armn"
},
"ia": {
"haswikt": 1
},
"id": {
"haswikt": 1
},
"ie": {
"haswikt": 1
},
"ik": {
"haswikt": 1
},
"ike": {
"sc": "Cans"
},
"ikt": {
"sc": "Cans"
},
"io": {
"haswikt": 1
},
"is": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"it": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"iu": {
"haswikt": 1,
"sc": "Cans"
},
"ja": {
"haswikt": 1,
"sc": "Jpan"
},
"jay": {
"sc": "Latn"
},
"jbo": {
"haswikt": 1
},
"jv": {
"haswikt": 1
},
"ka": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"khb": {
"sc": [
"Talu",
"Lana",
"Thai"
]
},
"kjh": {
"sc": "Cyrl"
},
"kk": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"kkh": {
"sc": [
"Lana",
"Thai"
]
},
"kl": {
"haswikt": 1
},
"km": {
"haswikt": 1,
"sc": "Khmr"
},
"kn": {
"haswikt": 1,
"sc": "Knda"
},
"ku": {
"g": [
"n",
"m",
"mnmane",
"mndudil",
"n-p",
"m-p",
"p"
],
"wiktprefix": "ku"
},
"ko": {
"haswikt": 1,
"sc": "Kore"
},
"krc": {
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"ks": {
"haswikt": 1,
"sc": [
"Arab",
"Deva"
],
"wsc": "ks-Arab"
},
"kw": {
"haswikt": 1
},
"ky": {
"haswikt": 1,
"g": [
"y",
"p"
],
"sc": "Cyrl"
},
"la": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"lb": {
"g": [
"n",
"m",
"nt",
"p"
],
"haswikt": 1,
"allowCaps": 1
},
"lez": {
"sc": "Cyrl"
},
"li": {
"haswikt": 1
},
"lki": {
"wiktprefix": "ku"
},
"lld": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lmo": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"ln": {
"haswikt": 1
},
"lo": {
"haswikt": 1,
"sc": "Laoo"
},
"lt": {
"alt": 1,
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lv": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"lzz": {
"sc": "Geor"
},
"mg": {
"haswikt": 1
},
"mh": {
"haswikt": 1
},
"mi": {
"g": 0,
"haswikt": 1
},
"mk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ml": {
"haswikt": 1,
"sc": "Mlym"
},
"mn": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": [
"Cyrl",
"Mong"
]
},
"mnp": {
"sc": "Hani"
},
"mo": {
"haswikt": 1,
"sc": "Cyrl"
},
"mol": {
"sc": "Cyrl"
},
"mr": {
"g": [
"n",
"m",
"nt"
],
"haswikt": 1,
"sc": "Deva"
},
"ms": {
"haswikt": 1,
"sc": [
"Latn",
"Arab"
]
},
"mt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"mwp": {
"sc": "Latn"
},
"my": {
"haswikt": 1,
"sc": "Mymr"
},
"na": {
"haswikt": 1
},
"nah": {
"haswikt": 1
},
"nan": {
"wiktprefix": "zh-min-nan",
"sc": "Hani"
},
"nb": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"wiktprefix": "no"
},
"nds-de": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"nds-nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"ne": {
"haswikt": 1,
"sc": "Deva"
},
"nl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nn": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"no": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1
},
"nod": {
"sc": "Lana"
},
"non": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"oc": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"om": {
"haswikt": 1
},
"or": {
"haswikt": 1,
"sc": "Orya"
},
"orv": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrs"
},
"osc": {
"sc": "Ital"
},
"ota": {
"g": [
"y",
"p"
],
"sc": "Arab",
"wsc": "ota-Arab"
},
"pa": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": [
"Guru",
"Arab"
]
},
"pdt": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "nds"
},
"peo": {
"sc": "Xpeo"
},
"phn": {
"sc": "Phnx"
},
"pi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1
},
"pjt": {
"sc": "Latn"
},
"pl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"pms": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"pox": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"împf",
"pf"
]
},
"prs": {
"wiktprefix": "fa",
"sc": "Arab",
"wsc": "fa-Arab"
},
"ps": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ps-Arab"
},
"pt": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1
},
"qu": {
"haswikt": 1
},
"rgn": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"rit": {
"sc": "Latn"
},
"rm": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"rn": {
"haswikt": 1
},
"ro": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"roa-rup": {
"haswikt": 1
},
"ru": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"rue": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"sc": "Cyrl"
},
"ruo": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rup": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"wiktprefix": "roa-rup"
},
"ruq": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"rw": {
"haswikt": 1
},
"sa": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
],
"haswikt": 1,
"sc": "Deva"
},
"sah": {
"sc": "Cyrl"
},
"sc": {
"haswikt": 1
},
"scn": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"sco": {
"sc": "Latn"
},
"sd": {
"haswikt": 1,
"sc": "Arab",
"wsc": "sd-Arab"
},
"sdh": {
"sc": "Arab",
"wiktprefix": "ku"
},
"sg": {
"haswikt": 1
},
"sh": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": [
"Latn",
"Cyrl"
]
},
"si": {
"haswikt": 1,
"sc": "Sinh"
},
"simple": {
"haswikt": 1
},
"sk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sl": {
"alt": 1,
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1
},
"sm": {
"haswikt": 1
},
"sn": {
"haswikt": 1
},
"so": {
"haswikt": 1
},
"spx": {
"sc": "Ital"
},
"sq": {
"g": [
"n",
"m"
],
"haswikt": 1
},
"ss": {
"haswikt": 1
},
"st": {
"haswikt": 1
},
"su": {
"haswikt": 1
},
"sux": {
"sc": "Xsux"
},
"sv": {
"g": [
"g",
"nt",
"g-p",
"nt-p"
],
"haswikt": 1
},
"sva": {
"sc": "Geor"
},
"sw": {
"nclass": 1,
"haswikt": 1
},
"syc": {
"sc": "Syrc"
},
"syr": {
"sc": "Syrc"
},
"szl": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
]
},
"ta": {
"haswikt": 1,
"sc": "Taml"
},
"tdd": {
"sc": "Tale"
},
"te": {
"haswikt": 1,
"sc": "Telu"
},
"tg": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Cyrl"
},
"th": {
"haswikt": 1,
"sc": "Thai"
},
"ti": {
"haswikt": 1,
"sc": "Ethi"
},
"tig": {
"sc": "Ethi"
},
"tiw": {
"sc": "Latn"
},
"tk": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tl": {
"haswikt": 1,
"sc": [
"Latn",
"Tglg"
]
},
"tmr": {
"sc": "Hebr"
},
"tn": {
"haswikt": 1
},
"to": {
"haswikt": 1
},
"tpi": {
"haswikt": 1
},
"tr": {
"g": [
"y",
"p"
],
"alt": 1,
"haswikt": 1
},
"ts": {
"haswikt": 1
},
"tt": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"tts": {
"sc": "Thai"
},
"tw": {
"haswikt": 1
},
"udi": {
"sc": [
"Cyrl",
"Latn",
"Armn",
"Geor"
]
},
"ug": {
"g": [
"y",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ug-Arab"
},
"uga": {
"sc": "Ugar"
},
"uk": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p",
"împf",
"pf"
],
"haswikt": 1,
"sc": "Cyrl"
},
"ulk": {
"sc": "Latn"
},
"ur": {
"g": [
"n",
"m",
"n-p",
"m-p",
"p"
],
"haswikt": 1,
"sc": "Arab",
"wsc": "ur-Arab"
},
"uz": {
"g": [
"y",
"p"
],
"haswikt": 1
},
"vec": {
"g": [
"n",
"m",
"n-p",
"m-p"
],
"haswikt": 1
},
"vi": {
"haswikt": 1
},
"vls": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p"
]
},
"vo": {
"haswikt": 1
},
"wa": {
"haswikt": 1
},
"wbp": {
"sc": "Latn"
},
"wo": {
"haswikt": 1
},
"wuu": {
"sc": "Hani"
},
"xae": {
"sc": "Ital"
},
"xcr": {
"sc": "Cari"
},
"xfa": {
"sc": "Ital"
},
"xh": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"xlc": {
"sc": "Lyci"
},
"xld": {
"sc": "Lydi"
},
"xlu": {
"sc": "Xsux"
},
"xmf": {
"sc": "Geor"
},
"xno": {
"g": [
"n",
"m",
"n-p",
"m-p"
]
},
"xrr": {
"sc": "Ital"
},
"xst": {
"sc": "Ethi"
},
"xum": {
"sc": "Ital"
},
"xve": {
"sc": "Ital"
},
"xvo": {
"sc": "Ital"
},
"yi": {
"g": [
"n",
"m",
"nt",
"n-p",
"m-p",
"nt-p",
"p"
],
"haswikt": 1,
"sc": "Hebr"
},
"yo": {
"haswikt": 1
},
"yua": {
"g": [
"y",
"p"
],
"alt": 1
},
"yue": {
"sc": "Hani"
},
"za": {
"haswikt": 1,
"sc": "Latn"
},
"zh-classical": {
"sc": "Hani"
},
"zh-min-nan": {
"haswikt": 1
},
"zh-yue": {
"sc": "Hani"
},
"zu": {
"alt": 1,
"nclass": 1,
"haswikt": 1
},
"zza": {
"g": [
"n",
"m",
"mn",
"n-p",
"m-p",
"p"
],
"wiktprefix": "diq"
}
},
"ziman": {
"ku": "kurmancî",
"en": "îngilîzî",
"tr": "tirkî",
"de": "almanî",
"pl": "polonî",
"ja": "japonî",
"fa": "farisî",
"sh": "sirboxirwatî",
"fr": "fransî",
"ka": "gurcî",
"it": "îtalî",
"fi": "fînî",
"ru": "rusî",
"es": "spanî",
"ar": "erebî",
"nl": "holendî",
"hi": "hindî",
"cs": "çekî",
"cmn": "mandarînî",
"pt": "portugalî",
"uk": "ûkraynî",
"hu": "mecarî",
"ko": "koreyî",
"be": "belarusî",
"la": "latînî",
"ckb": "soranî",
"sv": "swêdî",
"hy": "ermenî",
"bg": "bulgarî",
"ro": "romanyayî",
"da": "danmarkî",
"gl": "galîsî",
"el": "yûnanî",
"eo": "esperantoyî",
"tl": "tagalogî",
"fo": "ferî",
"vi": "viyetnamî",
"ca": "katalanî",
"is": "îzlendî",
"et": "estonî",
"az": "azerî",
"ceb": "sebwanoyî",
"mk": "makedonî",
"he": "îbranî",
"th": "tayî",
"lv": "latviyayî",
"sk": "slovakî",
"ms": "malezî",
"ga": "îrlendî",
"tg": "tacikî",
"grc": "yûnaniya kevn",
"sl": "slovenî",
"kk": "qazaxî",
"mi": "maorî",
"mn": "mongolî",
"sq": "albanî",
"uz": "ozbekî",
"lt": "lîtwanî",
"ur": "urdûyî",
"km": "ximêrî",
"id": "endonezyayî",
"cy": "weylsî",
"lo": "lawsî",
"bn": "bengalî",
"zza": "zazakî",
"ky": "kirgizî",
"yi": "yidîşî",
"gd": "gaelîka skotî",
"my": "burmayî",
"te": "telûgûyî",
"nb": "norweciya bokmålî",
"zh": "çînî",
"ps": "peştûyî",
"eu": "baskî",
"oc": "oksîtanî",
"sw": "swahîlî",
"ast": "astûrî",
"af": "afrîkansî",
"tk": "tirkmenî",
"nn": "norweciya nînorskî",
"yue": "kantonî",
"ug": "oygurî",
"rm": "romancî",
"sc": "sardînî",
"vo": "volapûkî",
"io": "îdoyî",
"ml": "malayalamî",
"mt": "maltayî",
"ba": "başkîrî",
"ta": "tamîlî",
"ang": "îngilîziya kevn",
"sa": "sanskrîtî",
"tt": "teterî",
"rup": "aromanî",
"nrf": "normandî",
"no": "norwecî",
"scn": "sicîlî",
"br": "bretonî",
"as": "asamî",
"bo": "tîbetî",
"nan": "minnanî",
"nv": "navajoyî",
"fy": "frîsî",
"pa": "puncabî",
"vec": "venîsî",
"fur": "friyolî",
"wa": "walonî",
"haw": "hawayî",
"lb": "luksembûrgî",
"am": "amharî",
"arz": "erebiya misrî",
"ia": "înterlîngua",
"kl": "kalalîsûtî",
"mr": "maratî",
"gv": "manksî",
"qu": "keçwayî",
"dsb": "sorbiya jêrîn",
"si": "kîngalî",
"kn": "kannadayî",
"sdh": "kurdiya başûrî",
"ota": "osmanî",
"gu": "gujaratî",
"ce": "çeçenî",
"jv": "javayî",
"pdt": "plodîşî",
"chr": "çerokî",
"nds": "nedersaksî",
"acw": "erebiya hîcazî",
"cu": "slaviya kevn",
"ne": "nepalî",
"kw": "kornî",
"hsb": "sorbiya jorîn",
"za": "zhuangî",
"os": "osetî",
"se": "samiya bakurî",
"dng": "dunganî",
"izh": "îngriyî",
"sco": "skotî",
"got": "gotî",
"or": "oriyayî",
"mnc": "mançûyî",
"crh": "teteriya krîmî",
"ltg": "latgalî",
"sah": "yakutî",
"ht": "haîtî",
"ary": "erebiya fasî",
"ha": "hawsayî",
"zu": "zuluyî",
"cv": "çuvaşî",
"yo": "yorubayî",
"ab": "abxazî",
"so": "somalî",
"frr": "frîsiya bakur",
"lad": "ladînoyî",
"an": "aragonî",
"ti": "tigrînî",
"aii": "aramiya nû ya asûrî",
"hak": "hakkayî",
"enm": "îngilîziya navîn",
"pi": "palî",
"navz": "navzimanî",
"ny": "çîçewayî",
"om": "oromoyî",
"nds-de": "almaniya jêrîn a almanî",
"lmo": "lombardî",
"csb": "kaşûbî",
"alt": "altayiya başûrî",
"arc": "aramî",
"bcl": "bîkoliya naverast",
"co": "korsîkayî",
"oj": "ojibweyî",
"pap": "papyamentoyî",
"rue": "rusînî",
"av": "avarî",
"txb": "toxarî B",
"xal": "kalmîkî",
"cop": "qiptî",
"mg": "malagasî",
"nap": "napolîtanî",
"gsw": "elmanîşî",
"dlm": "dalmatî",
"gn": "guwaranî",
"orv": "slaviya rojavayî ya kevn",
"tyv": "tuvanî",
"dv": "divehî",
"ady": "adîgeyî",
"bua": "buryatî",
"wuu": "wuyî",
"pam": "kapampanganî",
"myv": "erzayî",
"udm": "udmurtî",
"khb": "lueyî",
"rom": "romanî",
"su": "sundanî",
"ilo": "îlokanoyî",
"syc": "siryaniya klasîk",
"zdj": "şîngazîcayî",
"cdo": "min dongî",
"evn": "evenkî",
"kum": "kumikî",
"bal": "belûçî",
"lld": "ladînî",
"inh": "îngûşî",
"sd": "sindhî",
"lez": "lezgînî",
"fro": "fransiya kevn",
"non": "norsiya kevn",
"liv": "lîvonî",
"tet": "tetûmî",
"tpi": "tokpisinî",
"shn": "şanî",
"wo": "wolofî",
"mrw": "maranaoyî",
"gld": "nanayî",
"nah": "nahwatlî",
"ki": "kîkûyûyî",
"st": "sotoyiya başûr",
"mww": "hmongî",
"lij": "lîgûrî",
"rgn": "romagnolî",
"li": "lîmbûrgî",
"sm": "samoayî",
"sga": "îrlendiya kevn",
"ks": "keşmîrî",
"pms": "piedmontîsî",
"ccc": "çamicuroyî",
"hil": "hîlîgaynonî",
"xmf": "megrelî",
"hrx": "hunsrikî",
"rif": "tarifitî",
"krc": "karaçay-balkarî",
"war": "warayî",
"mwl": "mîrandî",
"syl": "sîlhetî",
"ee": "eweyî",
"ln": "lingalayî",
"pdc": "almaniya Pennsylvania",
"goh": "almaniya bilind a kevn",
"tly": "talişî",
"nci": "nahwatliya klasîk",
"ckt": "çukçî",
"ain": "aynuyî",
"uga": "ugarîtî",
"xh": "xosayî",
"sms": "samiya skoltî",
"kea": "kapverdî",
"ajp": "erebiya başûrê şamê",
"kjh": "xakasî",
"iu": "înuîtî",
"dz": "conxayî",
"srn": "sirananî",
"vro": "voroyî",
"mdf": "mokşayî",
"xcl": "ermeniya kevn",
"mhr": "mariya rojhilatî",
"cic": "çîkasawî",
"esu": "yupîkiya navendî",
"cjs": "şorî",
"wym": "wîlamowî",
"apw": "apaçiya rojava",
"mba": "higaononî",
"kaw": "javayiya kevn",
"ovd": "elfdalî",
"to": "tongî",
"ist": "îstriyotî",
"szl": "silesî",
"yua": "mayayiya yukatekî",
"tsg": "tausugî",
"zea": "zêlandî",
"bar": "bayerî",
"luy": "luhyayî",
"mzn": "mazenderanî",
"mhn": "mòçenoyî",
"kxd": "bruneyî",
"lzz": "lazî",
"kbd": "kabardî",
"afb": "erebiya kendavî",
"krl": "karelî",
"eve": "evenî",
"stq": "frîsiya saterlandî",
"ak": "akanî",
"tab": "tabasaranî",
"sat": "santalî",
"fj": "fîjî",
"sva": "svanî",
"egy": "misrî",
"ig": "îgboyî",
"hac": "hewramî",
"ay": "aymarayî",
"gur": "farefareyî",
"rw": "kînyarwandayî",
"twf": "taosî",
"koi": "komî-permyakî",
"lkt": "lakotayî",
"akl": "aklanon",
"yai": "yagnobî",
"ryu": "okînawanî",
"rhg": "rohingyayî",
"vep": "vepsî",
"smn": "samiya înarî",
"egl": "emilî",
"ty": "tahîtî",
"mdh": "maguindanaoyî",
"gag": "gagawzî",
"tzm": "temazîxtiya atlasa navendî",
"sgs": "samogîtî",
"ff": "fulahî",
"lou": "kreyoliya fransî ya Louisiana",
"mel": "melanauyiya navendî",
"niv": "nîvxî",
"aa": "afarî",
"gez": "etiyopiya klasîk",
"ext": "extremaduranî",
"frp": "frankoprovansî",
"kpv": "komî-zîriyî",
"udi": "udî",
"sma": "samiya başûr",
"nog": "nogayî",
"akk": "akadî",
"lbe": "lakî",
"min": "mînangkabawî",
"kg": "kîkongoyî",
"ban": "balînî",
"dlg": "dolganî",
"ace": "açehî",
"pox": "polabî",
"ppl": "pîpîlî",
"mnz": "monî",
"kaa": "karakalpakî",
"kab": "kabîlî",
"vot": "votî",
"luo": "luoyî",
"sn": "şonayî",
"swb": "komorî",
"roa-tar": "tarantinoyî",
"lg": "lugandayî",
"nds-nl": "saksoniya jêrîn ya holendî",
"dtp": "dusuniya navendî",
"shi": "taşelhitî",
"msk": "mansakayî",
"rap": "rapanuyî",
"mer": "meruyî",
"ik": "înupîakî",
"prg": "prusiya kevn",
"chk": "çûkî",
"kam": "kambayî",
"chy": "çeyenî",
"nod": "tayiya bakurî",
"akz": "alabamayî",
"xag": "aghwanî",
"ase": "zimanê destan ê emerîkî",
"nmn": "ǃxóõ",
"gun": "mbya guaraniyî",
"cr": "krî",
"yag": "yamanayî",
"otk": "tirkiya kevn",
"sid": "sidamoyî",
"tum": "tumbukayî",
"pjt": "pîttcantcatcarayî",
"sux": "sûmerî",
"bbl": "batsbî",
"jje": "jejuyî",
"bm": "bambarayî",
"frm": "fransiya navîn",
"tpn": "tupînambayî",
"pal": "farisiya navîn",
"bi": "bîslamayî",
"ksw": "s'gaw karenî",
"ssy": "sahoyî",
"bdr": "bajwayiya kendava rojava",
"sne": "bau bidayuh",
"sjd": "samiya kildînî",
"na": "nawrûyî",
"pag": "pangasînanî",
"abq": "abazayî",
"tn": "tswanayî",
"osx": "saksoniya kevn",
"aqc": "arçî",
"xsr": "şerpayî",
"kok": "konkanî",
"kln": "kalencînî",
"win": "wînnebagoyî",
"dar": "dargwayî",
"mfe": "kreyoliya morîtanî",
"iba": "îbanî",
"ae": "avestayî",
"mos": "moreyî",
"arn": "mapuçî",
"ill": "îranunî",
"mrj": "mariya rojavayî",
"cst": "ohloniya bakur",
"blt": "tay dam",
"apc": "erebiya bakûrê şamê",
"gan": "ganî",
"ii": "yiyiya siçuwayî",
"xto": "toxarî A",
"wmw": "mwanî",
"avd": "elwîrî-wîderî",
"pro": "oksîtaniya kevn",
"pcc": "buyî",
"jra": "jarayî",
"new": "newarî",
"unm": "unamî",
"mic": "mîkmakî",
"cjm": "çamiya rojhilatî",
"sdn": "galûrîsî",
"lus": "mizoyî",
"cjy": "jinî",
"tli": "tlingitî",
"bug": "bugî",
"pcd": "pîkardî",
"hop": "hopîyî",
"ofs": "frîsiya kevn",
"tft": "ternateyî",
"roa-opt": "portugaliya kevn",
"gil": "kîrîbatî",
"kac": "cingphoyî",
"csm": "sierra mîwokiya navendî",
"sdc": "sassarîsî",
"ood": "o'odhamî",
"hsn": "xiangî",
"ruo": "îstro-romanyayî",
"mbb": "bukidnon manoboyiya rojavayî",
"bho": "bojpûrî",
"ani": "andî",
"kgp": "kayingangî",
"ss": "swazî",
"mnp": "min beî",
"aho": "ahomî",
"moe": "montagnaîsî",
"naq": "namayî",
"mvi": "miyakoyî",
"duu": "drungî",
"lut": "luşûtsîdî",
"sce": "dongşîangî",
"bqi": "bextiyarî",
"mrc": "marîkopayî",
"gmh": "almaniya bilind a navîn",
"cim": "cimbrî",
"aer": "arrernteyiya rojavayî",
"agj": "argobbayî",
"ium": "îu mienî",
"tay": "atayalî",
"ale": "alêwîtî",
"str": "saanîçî",
"zku": "kaurnayî",
"pnt": "yûnaniya pontî",
"gmw-cfr": "frankoniya navendî",
"bxd": "pelayî",
"ems": "alutîqî",
"mtq": "muongî",
"hit": "hîtîtî",
"mak": "makasarî",
"bjn": "banjarî",
"nay": "ngarrindjerî",
"dak": "dakotayî",
"yrk": "nenetsiya tundra",
"pac": "pacohî",
"ch": "çamoroyî",
"vls": "flamiya rojava",
"acm": "erebiya iraqî",
"smj": "samiya lule",
"lmy": "lamboyayî",
"jam": "kreyoliya jameîkî",
"yka": "yakanî",
"gmw-ecg": "almaniya navendî ya rojhilat",
"xsv": "sûdovî",
"cdm": "çepangî",
"bdk": "buduxî",
"isd": "îsnagî",
"zgh": "amazîxiya fasî",
"css": "ohloniya başûr",
"kqr": "kîmaragangî",
"glk": "gîlekî",
"aeb": "erebiya tunusî",
"lki": "lekî",
"kdr": "karaîmî",
"kld": "gamîlaraayî",
"tdd": "tayî nüa",
"xqa": "karaxanîdî",
"nso": "sotoyiya bakur",
"crs": "kreyoliya seyşelî",
"mvv": "tagal murutî",
"kim": "tofalarî",
"swg": "swabî",
"pau": "palawî",
"wrh": "wiradhurî",
"cho": "çoktawî",
"tig": "tigreyî",
"ivv": "îvatanî",
"ddg": "fatalukuyî",
"rmf": "romaniya kalo fînî",
"pih": "pîtkarnî-norfolkî",
"phn": "fenîkî",
"ktu": "kitubayî",
"mwr": "marwarî",
"wbp": "warlpirî",
"zun": "zuniyî",
"crk": "kriya bejayî",
"ebk": "bontociya rojhilatî",
"nup": "nupeyî",
"yoi": "yonagunî",
"atj": "atîkamekwî",
"duo": "dupaningan agtayî",
"kca": "xantî",
"dta": "daurî",
"rar": "rarotongî",
"abe": "abenakî",
"aot": "atong (Hindistan)",
"osa": "osageyî",
"ami": "amîs",
"sea": "semaî",
"mh": "marşalî",
"mad": "madurayî",
"sei": "sêriyî",
"moh": "mohawkî",
"meu": "hîrî motuyî",
"pwn": "paîwanî",
"okm": "koreyiya navîn",
"xug": "kunigamî",
"amu": "amuzgoyî",
"ine-pro": "proto-hindûewropî",
"tzo": "tzotzîlî",
"ksc": "kalingayiya başûrî",
"sib": "sebopî",
"uum": "ûrûmî",
"kjj": "khinalugî",
"pis": "pijînî",
"peo": "farisiya kevn",
"sg": "sangoyî",
"tyz": "tàyî",
"kpy": "koryakî",
"ruq": "megleno-romanyayî",
"abs": "ambonezî-malezî",
"mas": "masayî",
"bdq": "bahnarî",
"mkz": "makasaeyî",
"mag": "magahî",
"oaa": "orokî",
"din": "dinkayî",
"zlw-slv": "slovînsî",
"olo": "livvî",
"mai": "maithilî",
"nhn": "nahwatliya navendî",
"nan-tws": "teoçewî",
"wuh": "wutunhuayî",
"tpw": "tûpiya kevn",
"gcf": "kreyoliya antîlleyî",
"knb": "lubuagan kalingayî",
"ket": "ketî",
"pua": "purepeçayî",
"xbr": "kamberayî",
"bor": "bororoyî",
"rmy": "romaniya vlaks",
"tao": "yamî",
"arq": "erebiya cezayirî",
"brh": "brahuiyî",
"chn": "çinook jargonî",
"cbk": "çavacanoyî",
"tvo": "tidoreyî",
"wbk": "waigalî",
"nio": "nganasanî",
"bsh": "kamkata-viriyî",
"roa-gal": "galoyî",
"krj": "kinaray-ayî",
"gmy": "yûnaniya mîkenî",
"ctd": "tedim çinî",
"ude": "udiheyî",
"gmw-rfr": "frankoniya rhine",
"tvl": "tuvalûyî",
"ibg": "îbanagî",
"ask": "aşkunî",
"ccp": "çakmayî",
"ts": "tsongayî",
"sje": "pite samî",
"esh": "iştihardî",
"quc": "k'içe'",
"ho": "hirî motuyî",
"hif": "fiji hindiyî",
"orh": "oroqenî",
"ett": "etrûskî",
"zai": "îsthmus zapotekî",
"bla": "blakfotî",
"zlw-opl": "poloniya kevn",
"ses": "sonxayî",
"lis": "lisuyî",
"kr": "kanuriyî",
"bbc": "bataktobayî",
"umb": "umbunduyî",
"rys": "yaeyamayî",
"arp": "arapaho",
"bol": "boleyî",
"com": "komançî",
"ifk": "tuwali ifugaoyî",
"ve": "vendayî",
"kri": "krioyî",
"aoz": "uab meto",
"yxg": "yagarayî",
"zom": "zouyî",
"ttt": "tatî",
"ams": "amamî-oşîmayiya başûrî",
"tts": "isanî",
"igl": "îgalayî",
"xda": "darkinjungî",
"fkv": "kvenî",
"chg": "çaxatayî",
"xwo": "oyratiya nivîsî",
"cab": "garîfunayî",
"atv": "altayiya bakurî",
"tiw": "tiwî",
"fit": "meänkiyelî",
"mnk": "mandinkayî",
"yox": "yoronî",
"tfn": "dena'inayî",
"hur": "halkomelemî",
"ryn": "amamî-oşimayiya bakurî",
"rop": "kriolî",
"ckv": "kavalanî",
"ulc": "ulçî",
"hts": "hadzayî",
"anq": "jarawa",
"dgr": "dogrîbî",
"myp": "pîrahayî",
"xfa": "faliskanî",
"gin": "hinukhî",
"pqm": "malecite-passamaquoddyî",
"mni": "manipurî",
"txg": "tangutî",
"trv": "tarokoyî",
"kha": "khasiyî",
"odt": "holendiya kevn",
"tby": "tabaruyî",
"drg": "rungusî",
"yur": "yurokî",
"neg": "negidalî",
"mga": "îrlendiya navîn",
"gor": "gorontaloyî",
"tcy": "tuluyî",
"oac": "oroçî",
"nia": "nîasî",
"skb": "saekî",
"bth": "biatah bidayuhî",
"nwy": "nottoway-meherrînî",
"chc": "katawbayî",
"bgc": "haryanvî",
"tih": "timugon murutî",
"itl": "îtelmenî",
"gml": "almaniya jêrîn a navîn",
"apj": "xîkarîyayî",
"yao": "yaoyî",
"okn": "oki-no-erabu",
"juc": "jurçenî",
"bnn": "bununî",
"nmb": "nambasiya mezin",
"cuk": "kunayî",
"uby": "ûbîkî",
"bpy": "manîpûriya bîşnûpriyayî",
"alr": "alutor",
"nys": "nyungayî",
"niq": "nandî",
"nnt": "nanticokeyî",
"shy": "şawî",
"xum": "umbriyî",
"kzg": "kikaiyî",
"blk": "pa'o karen",
"khi-kun": "ǃkungî",
"amw": "neo-aramiya rojavayî",
"kyh": "karokî",
"arw": "arawak",
"rkt": "kamtayî",
"ijc": "îzonî",
"jct": "krymçakî",
"xpq": "mohegan-pequotî",
"lnd": "lundayehî",
"xnb": "kanakanabuyî",
"niu": "nîwî",
"wls": "walîsî",
"axm": "ermeniya navîn",
"kap": "bezhtayî",
"ykg": "yukaghiriya bakurî",
"kky": "guugu yimidhirrî",
"hnj": "hmongiya hêşîn",
"dtr": "lotudî",
"kla": "klamatî-modokî",
"fax": "falayî",
"sjt": "ter samî",
"nxq": "naxî",
"oge": "gurciya kevn",
"apm": "çîrîkawayî",
"tzj": "tzutucîlî",
"tbl": "tbolî",
"inc-oas": "asamiya zû",
"hai": "haidayî",
"sou": "taiya başûrî",
"osc": "oskanî",
"shh": "şoşonî",
"xmz": "morî bawah",
"sju": "ume samî",
"xpr": "partî",
"gaa": "gayî",
"nch": "nahwatliya huastecaya navendî",
"bsk": "buruşaskiyî",
"bzj": "kreyoliya belizeyî",
"nhx": "nahwatliya mekayapan",
"ktb": "kambaatayî",
"xnn": "kankanayiya bakurî",
"pot": "potawatomî",
"agr": "aguarunayî",
"bsb": "bisayayiya bruneyî",
"cja": "çamiya rojavayî",
"nov": "noviyalî",
"kbp": "kabiyeyî",
"tkn": "toku-no-şîmayî",
"lzh": "çîniya klasîk",
"caa": "ç'orti'",
"paw": "pawnî",
"wbl": "waxiyî",
"rut": "rutulî",
"lud": "ludianî",
"gon": "gondiyî",
"lsd": "lîşana denî",
"pyu": "puyumayî",
"bej": "bejayî",
"sxr": "saaroayî",
"bsq": "bassayî",
"cmg": "mongoliya klasîk",
"mus": "krîkî",
"sas": "sasakî",
"mlm": "mulamî",
"aio": "aytonî",
"nuk": "nootkayî",
"aiw": "aarî",
"enq": "engayî",
"chl": "cahuillayî",
"huz": "hunzibî",
"grt": "garoyî",
"lic": "hlaiyî",
"fia": "nobiinî",
"osp": "spaniya kevn",
"amm": "amayî",
"ote": "otomiya mezquital",
"roa-leo": "leonîzî",
"dum": "holendiya navîn",
"awa": "awadhî",
"otw": "ottawayî",
"xbc": "baxterî",
"bem": "bembayî",
"ira-pro": "proto-îranî",
"dbl": "diyirbalî",
"mcm": "kristangî",
"ctu": "çolî",
"grk-mar": "yûnaniya mariupol",
"nst": "tangsayî",
"cak": "kaqçikelî",
"pov": "kreyoliya guinea-bissau",
"mbf": "malayiya baba",
"xsy": "saisiyatî",
"bno": "asî",
"lbj": "ladakhiyî",
"adt": "adnyamatanhayî",
"ttj": "tooroyî",
"apy": "apalayî",
"pim": "powhatanî",
"roa-brg": "bûrgûndî",
"szy": "sakizayayî",
"tkl": "tokelawî",
"wlm": "weylsiya navîn",
"gdo": "godoberî",
"ayn": "erebiya sanaanî",
"ksk": "kansayî",
"aht": "ahtnayî",
"btx": "batakiya karo",
"jdt": "tatiya cihû",
"cni": "aşanînkayî",
"skr": "seraikî",
"iir-pro": "proto-hindûîranî",
"yrl": "nhêngatûyî",
"sip": "sîkkîmî",
"ie": "înterlîngue",
"bin": "edoyî",
"sac": "foksî",
"wni": "komoriya ndzwanî",
"btq": "batekî",
"bkd": "binukid",
"gmq-scy": "skanî",
"rah": "rabhayî",
"ddo": "tsezî",
"tuw-sol": "solonî",
"gni": "gooniyandiyî",
"rmn": "romaniya balkanî",
"roa-poi": "poitevin-saintongeaisî",
"mqm": "marquesana başûr",
"tsu": "tsouyî",
"yol": "yolayî",
"fud": "futunî",
"umu": "munsî",
"wam": "massaçusettî",
"bsy": "sabah bisayayî",
"prn": "prasunî",
"clm": "klallamî",
"ssf": "thaoyî",
"jbo": "lojbanî",
"nus": "nuerî",
"mhx": "lhao voyî",
"hmc": "huişui hmongiya navendî",
"lrc": "luriya bakurî",
"tiy": "tirurayî",
"kkh": "khünî",
"nfr": "nafaanrayî",
"zpq": "zapotekiya zoogoço",
"suk": "sukumayî",
"rml": "romaniya baltîkî",
"ncj": "nahwatliya bakurê puebla",
"nhg": "nahwatliya tetelcingo",
"ars": "erebiya necdî",
"bh": "biharî",
"caq": "car nicobareseyî",
"wlc": "mwalî komorî",
"apd": "erebiya sudanî",
"mop": "mayayiya mopanî",
"yaq": "yaquî",
"pon": "ponpeyî",
"kwk": "kwak'walayî",
"wlo": "wolioyî",
"nnr": "narunggayî",
"agx": "aghulî",
"dty": "doteliyî",
"ybh": "yakkhayî",
"azz": "puebla nahuatliya çiyayî",
"yuy": "yuguriya rojhilat",
"tqw": "tonkawayî",
"hss": "harsusiyî",
"mqu": "mandarî",
"maz": "mazahuayiya navendî",
"ife": "îfèî",
"kmk": "limos kalingayî",
"shs": "şuswapî",
"hid": "hidatsayî",
"brg": "baureyî",
"wrr": "wardamanî",
"kis": "kîsî",
"acy": "erebiya qibrisî",
"tin": "tindî",
"gmq-osw": "swêdiya kevn",
"yuf": "havasupaî-walapaî-yavapaî",
"xdk": "darûkî",
"wau": "waujayî",
"ljp": "lampungî",
"alu": "areareyî",
"ach": "açolî",
"bkm": "komiya kamerûnî",
"ngn": "ngwoyî",
"bsa": "abînomnî",
"der": "deoriyî",
"shp": "şipibo-coniboyî",
"ycl": "lolopoyî",
"qua": "kuapawî",
"gmq-pro": "proto-norsî",
"nnv": "nukunuyî",
"zko": "kottî",
"khv": "khvarşiyî",
"nij": "ngajuyî",
"yog": "yogadî",
"raw": "rawangî",
"mfy": "mayoyî",
"nd": "ndebeliya bakurî",
"oko": "koreyiya kevn",
"fon": "fonî",
"abx": "abaknonî",
"rad": "radeyî",
"klj": "khalajî",
"tru": "turoyo",
"nag": "naga pidginî",
"crl": "kriya rojhilat ya bakurî",
"zlw-ocs": "çekiya kevn",
"aau": "abauyî",
"mrq": "marquesana bakur",
"sia": "samiya akkalayî",
"mjg-mgl": "mongghulî",
"car": "galîbî karîbî",
"roa-tou": "torangî",
"rel": "rendilleyî",
"oma": "omaha-poncayî",
"rmq": "kaloyî",
"rmo": "romaniya sinte",
"lep": "lepçayî",
"guw": "gunî",
"knj": "akatekî",
"slr": "salarî",
"sem-srb": "erebiya başûrî ya kevn",
"pad": "paumarî",
"yap": "yapî",
"xul": "ngunawalî",
"kzj": "kadazaniya beravî",
"fan": "fangî (Bantu)",
"brx": "bodoyî (Hindistan)",
"ane": "xârâcùù",
"cro": "crowî",
"ram": "kanelayî",
"tea": "temiarî",
"cnk": "khumi çinî",
"tkr": "tsakhurî",
"ulk": "meryamî",
"tzh": "tzeltalî",
"avk": "kotavayî",
"mjg-mgr": "mangghuerî",
"xav": "xavanteyî",
"kmv": "kreyoliya fransî ya karipúna",
"aas": "aasayî",
"guz": "gusiiyî",
"erk": "efateyiya başûr",
"ngh": "nlûyî",
"cel-gau": "gaulisî",
"hz": "hereroyî",
"alq": "algonquinî",
"cay": "cayugayî",
"inz": "înesenyoyî",
"sgr": "sangisarî",
"iro-min": "mingoyî",
"dru": "rukaiyî",
"tta": "tuteloyî",
"lui": "luiseñoyî",
"mgm": "mambayî",
"xpm": "pumpokolî",
"kyj": "karaoyî",
"ksd": "tolaiyî",
"pkc": "baekceyî",
"pcm": "pidgîniya nîjeryayî",
"kjg": "khmuyî",
"cow": "cowlitzî",
"cyo": "cuyunonî",
"msn": "vuresî",
"jhi": "jehaiyî",
"map-bms": "banyumasanî",
"fla": "montana salişî",
"skd": "sierra miwokiya başûrî",
"xpu": "punîkî",
"tsd": "tsakonî",
"gue": "gurindjiyî",
"ksi": "krisayî",
"pni": "aohengî",
"par": "panamintî",
"ulb": "ulukwumî",
"miq": "miskitoyî",
"mcb": "maçiguengayî",
"kls": "kalaşayî",
"sdo": "bukar-sadung bidayuhî",
"nr": "ndebeliya başûrî",
"mch": "maquiritarî",
"msb": "masbatenyoyî",
"tsi": "tsimşî",
"gmq-gut": "gutnişî",
"tus": "tuskarorayî",
"lmw": "miwokiya golî",
"srs": "sarceeyî",
"zag": "zaghawayî",
"klb": "kiliwayî",
"kmc": "kamiya başûrî",
"beg": "belaîtî",
"inc-ash": "prakrîtiya aşokanît",
"kj": "kwanyamayî",
"ng": "ndongayî",
"irk": "îraqwî",
"grk-ita": "grîkoyî",
"aaq": "penobskotî",
"kek": "q'eqçiyî",
"ojb": "ojibweyiya bakurî",
"txu": "kayapó",
"one": "oneidayî",
"ktv": "katuyiya rojhilatî",
"nhe": "nahwatliya huastekaya rojhilatî",
"lwl": "lawayiya rojhilatî",
"lua": "luba-kasayî",
"bjb": "barngarla",
"acu": "açuarî",
"cof": "tsafikiyî",
"gwi": "gwiçinî",
"urk": "urak lawoi'",
"tar": "tarahumarayiya navendî",
"kva": "bagvalalî",
"hni": "haniyî",
"sek": "sekanî",
"cpg": "yûnaniya kapadokyayî",
"wmb": "wambayayî",
"xld": "lîdyayî",
"amn": "amanabî",
"haa": "hänî",
"cdy": "çadongî",
"akr": "arakî",
"kju": "kaşayayî",
"ada": "adangmeyî",
"alh": "alawayî",
"gdq": "mehriyî",
"tmo": "temoqî",
"kus": "kusaalî",
"dwu": "dhuwalî",
"pak": "parakanã",
"agg": "angorî",
"wao": "wappoyî",
"aji": "ajië",
"cas": "tsimaneyî",
"byq": "basayî",
"xls": "lusîtanî",
"gcr": "kreyoliya guwiyanîsî",
"apk": "apaçiya deştan",
"kut": "kutenaiyî",
"gmw-jdt": "danmarkiya jersey",
"hus": "wastekî",
"rej": "rejangî",
"dog": "dogonî",
"roa-lor": "lorrainî",
"emb": "embalohî",
"tnq": "taînoyî",
"crj": "kriya rojhilat ya başûrî",
"nuj": "nyolî (Uganda)",
"gmq-oda": "danmarkiya kevn",
"akv": "axvaxî",
"doi": "dogriyî",
"trp": "kokborokî",
"geh": "hutterisçî",
"gul": "gullahî",
"coo": "comoxî",
"ije": "biseniyî",
"itk": "îtaliya cihû",
"rmc": "romaniya karpatyayî",
"gmw-gts": "gottscheerîşî",
"ttg": "tutongî",
"ngu": "nahwatliya guerrero",
"pse": "malayiya navendî",
"onw": "nubiya kevn",
"qui": "kuwileytî",
"giw": "gelaoyiya spî",
"ojw": "ojibweyiya rojavayî",
"gay": "gayoyî",
"rmw": "romaniya welşî",
"nhw": "nahwatliya huastecaya rojavayî",
"xkl": "bakungî",
"loz": "loziyî",
"yij": "yindcîbarndî",
"kry": "krytsî",
"ple": "palueyî",
"tht": "tahltanî",
"mkq": "bay miwokî",
"sks": "maiayî",
"mhe": "besisî",
"mrv": "mangarevanî",
"nrm": "naromî",
"chp": "çîpewî",
"bll": "biloxî",
"knx": "kendayanî",
"khw": "khowarî",
"dua": "diwalayî",
"roa-cha": "şampanyî",
"gug": "guaraniyiya paraguayanî",
"dsn": "dusnerî",
"tmh": "tuaregî",
"pmf": "pamonayî",
"smq": "samoyî",
"tal": "talî",
"yuc": "yuçî",
"mqy": "manggarayî",
"bts": "batakiya simalungun",
"tos": "totonakiya çiyayî",
"oia": "oyratayî",
"blz": "balantak",
"bjz": "baruga",
"kai": "karekareyî",
"zav": "zapotekiya yatzaçî",
"okd": "okodiayî",
"ndd": "nde-nsele-nta",
"omc": "moçikayî",
"bto": "iriga bikolanoyî",
"okr": "kirikeyî",
"top": "totonakiya papantla",
"xer": "xerenteyî",
"xlc": "lîkî",
"mid": "mandaicî",
"suy": "suyayî",
"chz": "ozumacin çinantecî",
"pah": "tenharimî",
"til": "tillamookî",
"afn": "defakayî",
"guc": "wayuuyî",
"ojp": "japoniya kevn",
"lif": "limbuyî",
"efi": "efikî",
"mek": "mêkêoyî",
"ayl": "erebiya lîbyayî",
"bnt-phu": "phuthî",
"kne": "kankanaeyî",
"ktn": "karitiyanayî",
"abl": "abûngî",
"rtm": "rotumanî",
"wyb": "ngiyambaayî",
"crg": "mîçîfî",
"kxu": "kuiyî (Hindistan)",
"cts": "bicolanoyiya northern catanduanes",
"dje": "zarmayî",
"plw": "palawanoyî",
"xlu": "luwiyanî",
"koy": "koyukonî",
"vmw": "makhuwayî",
"vmf": "frankiya rojhilatî",
"bru": "bruyiya rojhilatî",
"kmb": "kimbunduyî",
"xsa": "sabaeanî",
"tbk": "tagbanwayiya kalamî",
"kzt": "tambunan dusunî",
"xng": "mongoliya navîn",
"acn": "açangî",
"bwx": "bu-nao bunuyî",
"cwg": "çewongî",
"vmb": "mbabaramî",
"ijs": "îjoyiya başûr-rojhilat",
"asb": "assînîboyî",
"nzd": "nzadî",
"itv": "îtawitî",
"lvk": "lavukaleveyî",
"bde": "badeyî",
"tbc": "takyayî",
"aby": "aneme wakeyî",
"urb": "urubú-kaaporî",
"coh": "çiçonyi-çidzihana-çikauma",
"dbj": "ida'anî",
"smk": "bolinaoyî",
"lbx": "lawanganî",
"kfr": "kaççiyî",
"hlt": "nga layî",
"agm": "angaatahayî",
"xpg": "firîgî",
"smr": "simeulueyî",
"vma": "martuthunirayî",
"gub": "guajajarayî",
"chf": "tabasco çontalî",
"mqs": "makyaniya rojava",
"iby": "îbaniyî",
"cng": "qiangiya bakurî",
"jac": "jakaltekî",
"trw": "torwalî",
"lti": "lêtîyî",
"scl": "şinayî",
"gwr": "gwereyî",
"dgc": "casiguran dumagat agtayî",
"btm": "batakiya mandaîlîng",
"puj": "punan tubuyî",
"oka": "okanaganî",
"gek": "gerkayî",
"mat": "matlatzincayî",
"kio": "kiowayî",
"cku": "koasatî",
"kms": "kamasauyî",
"xaa": "erebiya endulusî",
"gut": "maleku jaikayî",
"aou": "a'ou",
"elx": "îlamî",
"peh": "bonanî",
"pgl": "îrlendiya prîmîtîv",
"myx": "masabayî",
"spp": "supyireyî",
"xnt": "narragansettî",
"cad": "caddoyî",
"aap": "pará arárayî",
"rme": "angloromanî",
"ura": "urarinayî",
"mpm": "mixteciya yosondúa",
"poe": "popolocayiya san juan atzingo",
"sgd": "surigaononî",
"kvh": "komodoyî",
"xtc": "kaduglî",
"tjg": "tunjungî",
"azg": "san pedro amuzgos amuzgo",
"uun": "pazehî",
"udl": "uldemeyî",
"skp": "sekapanî",
"isi": "nkem-nkumî",
"wrz": "warrayî",
"its": "îsekiriyî",
"tgb": "tobilungî",
"bdy": "bandjalangî",
"ssb": "samayiya başûrî",
"hnn": "hanunooyî",
"enn": "engenniyî",
"srm": "saramakkanî",
"dmr": "damariya rojhilat",
"djo": "jangkangî",
"kij": "kîlîvîlayî",
"rob": "tae'",
"sad": "sandaweyî",
"har": "harariyî",
"hov": "hovonganî",
"pos": "popolucayiya sayula",
"sit-bok": "bokarî",
"kns": "kensiuyî",
"mbd": "dibabawon manoboyî",
"asu": "tocantins asurînî",
"sjw": "şawneeyî",
"btw": "butuanonî",
"jut": "jutişî",
"bgs": "tagabawayî",
"xsb": "sambalî",
"mkf": "miyayî",
"mrn": "çeke holoyî",
"kui": "kuikúroyî",
"hup": "hupayî",
"abm": "abanyomî",
"ukk": "muak sa-aakî",
"lbk": "bontociya navendî",
"dbp": "duwaiyî",
"jup": "hupdëî",
"cce": "çopiyî",
"muy": "muyangî",
"agt": "agtayiya kagayanî ya navendî",
"sxn": "sangirî",
"brc": "kreyoliya holendî ya berbiceyî",
"yux": "yukaghiriya başûrî",
"zpv": "zapotekiya çiçicapan",
"teo": "atesoyî",
"dif": "dieriyî",
"maq": "mazateciya çiquihuitlan",
"sgh": "şughnî",
"gnc": "guwançî",
"ega": "egayî",
"kbh": "camsayî",
"bni": "bangî",
"tdj": "tajioyî",
"ngi": "ngizimî",
"bue": "beothukî",
"pok": "pokangayî",
"myw": "muyuwî",
"bkn": "bukîtan",
"dyu": "dyulayî",
"asn": "xingú asuriní",
"cme": "cermayî",
"tna": "takanayî",
"mia": "miamî",
"epi": "epieyî",
"mcq": "eseyî",
"kyu": "kayahiya rojavayî",
"sai-pyk": "pykobjê",
"zaa": "zapotekiya sierra de juarez",
"dhv": "dehûyî",
"bxk": "bukusuyî",
"nuz": "nahwatliya tlamacazapa",
"trm": "tregamî",
"cps": "capiznonî",
"slm": "pangutaran samayî",
"cia": "cia-ciayî",
"abv": "erebiya bahranî",
"lod": "berawanî",
"mhj": "mogholî",
"mnv": "rennelleseyî",
"gir": "gelaoyiya sor",
"bfq": "badagayî",
"wyi": "woiwurrungî",
"eya": "eyakî",
"awk": "awabakal",
"zoc": "zoqueyiya copainala",
"smw": "sumbawayî",
"ank": "goemayî",
"mbl": "maxakalî",
"tbd": "kakî aeyî",
"nez": "nez perceyî",
"crn": "corayî",
"sai-tap": "tapayunayî",
"see": "senekayî",
"huv": "huaveyî",
"apn": "apinayé",
"mfa": "malayiya pattani",
"crx": "karriyerî",
"mam": "mamî",
"ibl": "îbaloiyî",
"cac": "çujî",
"kdt": "kuyî",
"ocu": "tlahuicayî",
"hvn": "sabuyî",
"cjp": "cabecarî",
"xbi": "kombioyî",
"xra": "krahô",
"blc": "bella koola",
"peq": "pomoyiya başûrî",
"bpz": "bilbayî",
"mzp": "movimayî",
"lhu": "lahuyî",
"kge": "komeringî",
"tue": "tuyukayî",
"taa": "tananaya jêrîn",
"vkp": "kreyoliya portugalî ya korlai",
"cmo": "mnongiya navendî",
"mov": "mojaveyî",
"kkz": "kaskayî",
"zro": "zaparoyî",
"mnb": "munayî",
"mwf": "murînpatayî",
"zkg": "goguryeoyî",
"kiw": "kiwaiyiya bakur-rojhilat",
"ebr": "ebrieyî",
"pmt": "tuamotuanî",
"roa-ang": "angevinî",
"ssg": "seimatî",
"hch": "huiçolî",
"bew": "betawî",
"ycn": "yucunayî",
"mbr": "nukak makuyî",
"apq": "a-pucikwar",
"nym": "nyamwezî",
"tbi": "gaamî",
"obi": "obispeñoyî",
"pri": "paicî",
"lu": "luba-katangayî",
"kcg": "tyapî",
"yui": "yurutî",
"kvm": "kendemî",
"djk": "aukanî",
"rir": "ribunî",
"lrg": "laragiayî",
"wnw": "wintuyî",
"woe": "woleayî",
"ljl": "li’oyî",
"kic": "kîkapûyî",
"ndh": "ndalî",
"hdy": "hadiyyayî",
"cbs": "caşinahuayî",
"kys": "baram kayanî",
"nxg": "ngadhayî",
"hix": "hixkaryanayî",
"mpe": "majangî",
"pwm": "molbogî",
"nyn": "nyankoleyî",
"ilb": "îlayî",
"qya": "qiwenyayî",
"wrk": "garawayî",
"def": "dezfuliyî",
"oym": "wayampî",
"rkb": "rikbaktsayî",
"sai-msk": "masakarayî",
"itz": "îtzayî",
"sgc": "kipsigisî",
"ppi": "paipaî",
"tji": "tujiayiya bakurî",
"yak": "yakîmayî",
"gvp": "para gaviãoyî",
"jmc": "maçameyî",
"bfs": "bayiya başûrî",
"ncb": "nicobariya navendî",
"kvb": "kubuyî",
"veo": "venturenyoyî",
"poi": "popolucayiya highland",
"bny": "bintuluyî",
"bhp": "bîmayî",
"rim": "nyaturuyî",
"sog": "sogdî",
"rth": "ratahanî",
"mse": "museyî",
"ncl": "nahwatliya mîçoakanê",
"acp": "akîpayiya rojhilatî",
"pao": "paiuteyiya bakurî",
"iyx": "yaayî",
"zhx-tai": "taişanîzî",
"coe": "koreguajeyî",
"ser": "serranoyî",
"nko": "nkonyayî",
"mgq": "malilayî",
"ppo": "folopayî",
"alc": "kawésqar",
"abg": "abagayî",
"wro": "wororayî",
"kre": "panarayî",
"tuv": "turkanayî",
"dav": "taitayî",
"ppk": "umayî",
"roa-oca": "katalaniya kevn",
"tso-lya": "çolyanî",
"myu": "mundurukú",
"dcc": "deccaniyî",
"lsi": "laşiyî",
"ugo": "gongî",
"nab": "nambikwarayî",
"lil": "lillooetî",
"gqi": "guiqiongî",
"gah": "alekanoyî",
"ncz": "natçezî",
"xom": "komoyî",
"prk": "paraukî",
"bea": "beaverî",
"sky": "sikayanayî",
"pui": "puinaveyî",
"kgg": "kusundayî",
"lml": "ragayî",
"ore": "orejónî",
"vbb": "babariya başûr-rojhilat",
"mbp": "wiwayî",
"bft": "baltî",
"lgg": "lugbarayî",
"mxd": "modangî",
"mir": "isthmus mixeyî",
"enf": "forest enetsî",
"pkp": "pukapukanî",
"kzi": "kelabît bariyoyî",
"wba": "waraoyî",
"xeg": "ǁxegwî",
"cpa": "palantla çinantecî",
"men": "mendeyî",
"btu": "batuyî",
"psy": "piscatawayî",
"xhu": "hurî",
"for": "foreyî",
"deg": "degemayî",
"sai-oto": "otomacoyî",
"nhv": "nahwatliya temascaltepec",
"gme-cgo": "gothîkiya krimî",
"kyi": "kîputî",
"olt": "lîtwanyayiya kevn",
"alp": "aluneyî",
"nbb": "ndoeyî",
"uvl": "loteyî",
"pab": "parecî",
"nbh": "ngamoyî",
"dra-okn": "kannadayiya kevn",
"aak": "ankaveyî",
"wiv": "vîtûyî",
"kuz": "kunzayî",
"atb": "zaiwa",
"aca": "açaguwayî",
"zmr": "maranungkuyî",
"xke": "kerehoyî",
"isk": "îşkaşimiyî",
"dig": "digoyî",
"agn": "agutaynenî",
"bji": "burjî",
"klw": "linduyî",
"bwi": "baniwayî",
"ygr": "yagariayî",
"mez": "menomineeyî",
"kag": "kajamanî",
"mey": "hassaniyayî",
"umo": "umotinayî",
"yii": "yidinyî",
"twy": "tawoyanî",
"ktw": "katoyî",
"ari": "arîkara",
"ysr": "sirenikî",
"adz": "adzerayî",
"spo": "spokanî",
"cgg": "rukîgayî",
"wuv": "wuvulu-auayî",
"etu": "ejaghamî",
"jmd": "yamdenayî",
"mhy": "ma'anyanî",
"tae": "taryanayî",
"kzr": "karangî",
"zhn": "nong zhuangî",
"ess": "yupikiya sîberyaya navendî",
"teg": "teke-tegeyî",
"cqd": "çuanqiandian cluster miaoyî",
"sri": "sirianoyî",
"dag": "dagbaniyî",
"gae": "warekenayî",
"lre": "laurentî",
"tbt": "kitemboyî",
"pgn": "paelignianî",
"onb": "lingaoyî",
"pth": "pataxó hã-ha",
"vmg": "minigirî",
"yah": "yazghulamî",
"kxn": "kanowitî",
"kde": "makondeyî",
"api": "apiaká",
"och": "çîniya kevn",
"pre": "prînsîpenî",
"mgr": "mambwe-lunguyî",
"stp": "tepehuaniya başûr-rojhilat",
"kak": "kayapa kallahanî",
"myg": "mantayî",
"kee": "keresiya rojhilatî",
"mfh": "matalî",
"bkr": "bakumpaî",
"alw": "alaba",
"pml": "sabirî",
"rag": "logûlî",
"mei": "midobî",
"mjy": "mahicanî",
"blm": "belî",
"nhu": "nooneyî",
"kuf": "katuyiya rojavayî",
"cux": "tepeuxila cuicatecî",
"nyy": "nyakyusayî",
"mtf": "murikî (Gîneya nû)",
"mrt": "margî",
"mpj": "martu wangkayî",
"tlh": "klîngonî",
"enh": "tundra enetsî",
"sai-crd": "coroado purî",
"umi": "ukitî",
"tcb": "tanakrosî",
"kpt": "karatayî",
"xvn": "vandalî",
"xsm": "kasemî",
"ltc": "çîniya navîn",
"yuz": "yuracareyî",
"bhw": "biyakî",
"sqt": "soqotrî",
"dny": "deniyî",
"lcp": "lawayiya rojavayî",
"tpx": "akatepekiya me'phaayî",
"ake": "akawaîo",
"jeb": "jeberoyî",
"gup": "gunwingguyî",
"xeb": "eblaiteyî",
"zkt": "xîtanî",
"lex": "luangî",
"mnq": "minriqî",
"njm": "angamî",
"sge": "segayî",
"hoi": "holîkaçukî",
"tee": "huehuetla tepehuayî",
"bil": "bileyî",
"gej": "genî",
"wim": "wik-mungkanî",
"oyd": "oydayî",
"gvj": "guajayî",
"blw": "balangao",
"lgt": "pahîyî",
"sai-cac": "cacanî",
"xok": "xoklengî",
"knv": "taboyî",
"slc": "salibayî (Kolombiya)",
"noz": "nayî",
"bnq": "bantîkî",
"ykm": "yakamulî",
"dym": "yanda dogonî",
"cri": "sãotomenseyî",
"shu": "erebiya çadî",
"tcs": "creoleyiya torres strait",
"gmw-stm": "sathmar swabî",
"rbl": "miraya bikolî",
"mbn": "macaguanî",
"mgu": "mailuyî",
"pib": "yineyî",
"yku": "kuamasî",
"gva": "kaskihayî",
"sai-yur": "yurumanguî",
"clw": "çulymî",
"bln": "bîkolanoyiya başûr katanduanî",
"mxi": "mozarabîkî",
"sob": "sobeyî",
"agd": "agarabiyî",
"bxe": "ongotayî",
"ito": "îtonamayî",
"mmq": "aisî",
"gnd": "zulgo-gemzekî",
"wig": "wik-ngathanayî",
"dto": "tommo soyî",
"suz": "sunwarî",
"rol": "romblomanonî",
"llu": "lawî",
"jaa": "jamamadiyî",
"bns": "bundelî",
"wal": "wolayttayî",
"oui": "oyguriya kevn",
"bjt": "balanta-ganjayî",
"alk": "alakî",
"nhr": "naroyî",
"gmw-vog": "almaniya volga",
"nsn": "nehanî",
"kqe": "kalaganî",
"end": "endeyî",
"jui": "ngadcurî",
"hay": "hayayî",
"svb": "ulau-suainî",
"saz": "sauraştrayî",
"mep": "miriwungî",
"ert": "eritaiyî",
"yen": "yendangî",
"mxb": "mixteciya tezoatlan",
"pmw": "plains miwokî",
"fak": "fangî (Beboid)",
"sai-bet": "betoî",
"arl": "arabela",
"cks": "tayoyî",
"nrn": "nornî",
"kao": "kassonkeyî",
"blb": "bilua",
"wab": "wabî",
"kwd": "kwaioyî",
"abr": "abronî",
"wbi": "wanjî",
"cso": "soçiapam çinantecî",
"kmo": "kwomayî",
"ddw": "dawera-daweloorî",
"zae": "zapotekiya yareni",
"rac": "rasawayî",
"bra": "brajî",
"naz": "nahwatliya koatepek",
"qwm": "kipçakî",
"ayz": "maybratî",
"amp": "alamblakî",
"sit-liz": "lizuyî",
"jum": "jumjumî",
"tub": "tübatulabalî",
"hih": "pamosuyî",
"kbc": "kadiweuyî",
"tun": "tunikayî",
"cod": "cocamayî",
"abt": "ambulasî",
"ame": "yaneşa'",
"xed": "hdî",
"yrb": "yarebayî",
"mbc": "macuşî",
"sai-mal": "malalî",
"khs": "kasuayî",
"soz": "temî",
"mva": "manamî",
"bcu": "bîlîwayî",
"zpo": "zapotekiya amatlan",
"tfr": "teribeyî",
"xgf": "gabrielino-fernandeñoyî",
"aah": "abu' arapeşî",
"adn": "adangî",
"btn": "ratagnonî",
"gby": "gbariyî",
"sxb": "subayî",
"onn": "onobasuluyî",
"aih": "aî-çamî",
"djm": "jamsayî",
"wun": "bunguyî",
"bhv": "bahauyî",
"ahk": "akha",
"tow": "jemezî",
"duw": "dusun wituyî",
"alj": "alanganî",
"sbk": "safwayî",
"kho": "xotanî",
"ndj": "ndambayî",
"mmh": "mehinakuyî",
"ttw": "long watî",
"sai-cat": "catacaoyî",
"kck": "kalangayî",
"yav": "yangbenî",
"heh": "heheyî",
"szw": "sawayî",
"phk": "phakeyî",
"aoa": "angolar",
"xur": "urartûyî",
"txn": "taranganiya rojava",
"piv": "pilenî",
"xta": "mixtekiya alcozauca",
"ute": "uteyî",
"tad": "tauseyî",
"yug": "yugî",
"zum": "kumzarî",
"nkr": "nukuoroyî",
"xaw": "kawaiisuyî",
"mpa": "mpotoyî",
"zng": "mangî",
"gad": "gaddangî",
"drn": "damariya rojava",
"oca": "ocainayî",
"mcg": "mapoyoyî",
"gbm": "garhwaliyî",
"sbh": "sorî-harenganî",
"kew": "kewayî",
"mki": "dhatkî",
"obt": "bretoniya kevn",
"alo": "larike-wakasihu",
"drl": "baagandjiyî",
"tog": "tongayî (Malawî)",
"tdl": "surî",
"met": "matoyî",
"cle": "lealao çinantecî",
"gmq-jmk": "jamtîşî",
"gvf": "golinî",
"tlc": "totonakiya misantla",
"bfg": "busang kayanî",
"kvc": "kovêyî",
"kay": "kamayurayî",
"hei": "heiltsukî",
"urt": "uratî",
"nll": "nihalî",
"bdp": "bendeyî",
"ybe": "yuguriya rojavayî",
"nbr": "numanayî",
"mht": "mandahuacayî",
"wap": "wapişanayî",
"dzg": "dazagayî",
"wrg": "warunguyî",
"tiv": "tivî",
"tox": "tobî",
"kvo": "dobelî",
"ubl": "buhi'non bikolî",
"aja": "ajayî",
"toi": "tongayî (Zambiya)",
"sus": "susuyî",
"nhm": "nahwatliya morelos",
"mcf": "matsesî",
"lew": "ledo kailiyî",
"ckx": "cakayî",
"bxg": "bangalayî",
"nem": "nemî",
"mgo": "meta'",
"eno": "engganoyî",
"crd": "coeur d'aleneyî",
"bdb": "basapî",
"cui": "cuibayî",
"emx": "erromintxelayî",
"bsw": "baysoyî",
"piw": "pimbweyî",
"nim": "nilambayî",
"mzs": "makanîzî",
"njo": "aoyî",
"duk": "uyajitayayî",
"old": "moçî",
"sam": "aramiya samarî",
"mlq": "malinkî",
"awd-kaw": "kawişanayî",
"mro": "mruyî",
"sjm": "mapunî",
"bot": "bongoyî",
"lay": "lama baiyî",
"aif": "agî",
"bks": "masbate sorsogon",
"ibd": "îwaidjayî",
"dob": "dobûyî",
"noa": "woun meuyî",
"too": "totonakiya xicotepec de juarez",
"duv": "duvleyî",
"bqo": "baloyî",
"mna": "mbulayî",
"pbh": "panareyî",
"cav": "cavineñayî",
"scg": "sanggauyî",
"con": "cofanî",
"ubr": "ubirî",
"ata": "pele-ata",
"ngj": "ngieyî",
"ers": "ersuyî",
"tro": "taraoyî",
"yki": "yokeyî",
"tya": "tauyayî",
"byk": "biaoyî",
"tuq": "tedagayî",
"xrn": "arinî",
"mhq": "mandanî",
"ktx": "kaxarariyî",
"beu": "blagarî",
"mtv": "asaro'oyî",
"zca": "zapotekiya coatecas altas",
"abd": "manîdeyî",
"imn": "îmondayî",
"ofo": "ofoyî",
"avv": "avá-kanoeîro",
"ree": "rejang kayanî",
"klr": "khalingî",
"dby": "dibiyasoyî",
"bzd": "bribriyî",
"iwm": "îwamî",
"bvk": "bukatî",
"mgw": "matumbî",
"aus-wul": "wulguruyî",
"mto": "totontepec mixeyî",
"inc-ogu": "gujaratiya kevn",
"kpm": "kohoyî",
"bgz": "banggaî",
"klg": "tagakaulu kalaganî",
"xab": "sambeyî",
"pnz": "panayî (Afrîkaya navendî)",
"snl": "sangilî",
"aoc": "pemon",
"cul": "culinayî",
"nnq": "ngindoyî",
"cji": "çamalalî",
"urh": "urhoboyî",
"hoc": "hoyî",
"yok-nvy": "yokutiya geliyê bakurî",
"nbp": "nnamî",
"qyp": "kuiripî",
"ski": "sikayî",
"whk": "wahau kenyahî",
"fos": "sirayayî",
"paa-nun": "nungonî",
"mtd": "mualangî",
"mrh": "mara çinî",
"xeu": "keoru-ahiayî",
"ass": "îpulo",
"ing": "deg xinagî",
"ajg": "adjayî",
"kjl": "khamî",
"wed": "wedauyî",
"mxz": "maselayiya navendî",
"miw": "akoyeyî",
"lmc": "limilnganî",
"bag": "tukî",
"bva": "bareinî",
"yal": "yalunkayî",
"asa": "pare",
"sai-col": "colanî",
"kdd": "yankunytjatjarayî",
"pne": "penaniya rojavayî",
"hna": "minayî",
"ayp": "erebiya mezopotamyayî ya bakur",
"can": "çambriyî",
"yey": "yeyî",
"szc": "semaq beriyî",
"byd": "benyadu'",
"yok-svy": "yokutiya geliyê başûrî",
"xet": "setayî",
"aro": "araona",
"ntw": "nottowayî",
"szv": "isubuyî",
"crw": "çrauyî",
"kyz": "kayabiyî",
"asl": "asîluluyî",
"poz-abi": "abay-sembwakî",
"tsx": "mubamî",
"ify": "keley-i kallahanî",
"srv": "waray sorsogonî",
"ong": "oloyî",
"mvm": "muyayî",
"lam": "lambayî",
"icl": "zimanê destan ê îzlandê",
"xww": "wemba-wembayî",
"ixl": "îxilî",
"ape": "bukiyîpî",
"rui": "rufijî",
"mhl": "mauwakeyî",
"lgr": "lengoyî",
"nmy": "namuyî",
"ncn": "naunayî",
"pez": "penaniya rojhilatî",
"mfg": "mixiforeyî",
"bei": "bekatî'",
"ydg": "yidghayî",
"cwd": "kriya woods",
"mmn": "mamanwayî",
"nai-kat": "kathlametî",
"cje": "çruyî",
"ark": "arîkapûyî",
"fut": "futuna-anîwayî",
"wad": "wandamenî",
"tew": "tewayî",
"nyo": "nyoroyî",
"bky": "bokyî",
"tjm": "timucuayî",
"ysg": "sonagayî",
"ogu": "ogbronuagumî",
"mnj": "munjî",
"gwd": "gawwadayî",
"fbl": "west albay bikolî",
"uiv": "îyiveyî",
"bkq": "bakairí",
"ibe": "akpesî",
"maa": "mazateciya san jerónimo tecóatl",
"jig": "jinguluyî",
"tvd": "tsuvadî",
"xod": "kokodayî",
"adw": "amondawayî",
"guu": "yanomamöî",
"srh": "sarikolî",
"nam": "ngan'gityemerrî",
"hto": "minica huitotoyî",
"yby": "yaweyuhayî",
"urz": "uru-eu-wau-wau",
"ewo": "ewondoyî",
"cal": "karolînî",
"arr": "arara-karo",
"yup": "yukpayî",
"iai": "îaaiyî",
"wbh": "wandayî",
"dmw": "mudburrayî",
"xel": "keloyî",
"rma": "ramayî",
"zwa": "zayî",
"emi": "mûssaû-emîrakî",
"lai": "lambyayî",
"dim": "dimeyî",
"agu": "agwakatekî",
"zin": "zinzayî",
"kiv": "kimbuyî",
"dbg": "dogul domî",
"bzg": "babuzayî",
"roa-ole": "leoniya kevn",
"bun": "şerbroyî",
"gnn": "gumatjî",
"mhs": "bûrî",
"pln": "palenqueroyî",
"aus-guw": "guwarî",
"tgt": "tagbanwayiya navendî",
"mwv": "mentawaî",
"kke": "kakabeyî",
"pla": "mianî",
"bpr": "koronadal blaanî",
"oty": "tamîliya kevn",
"ywr": "yawuruyî",
"nai-tap": "tapaçultecî",
"sai-sec": "seçurayî",
"pid": "piaroayî",
"mih": "mixteciya çayuco",
"icr": "kreyoliya îngilîzî ya îslanderê",
"hoh": "hobyótî",
"fab": "annoboneseyî",
"ssm": "semnamî",
"ncg": "nisga'ayî",
"mkg": "makî (Çîn)",
"max": "malayiya moluccana bakur",
"zak": "zanakî",
"kxv": "kuviyî",
"xkn": "kayan river kayanî",
"fuy": "fuyugî",
"zpl": "zapotekiya laçixio",
"kwf": "kwara'aeyî",
"aig": "îngilîziya kreyolî ya antîgua û barbud",
"rai": "ramoaainayî",
"otd": "dohoyî",
"bey": "akuwagelî",
"nwr": "nawaruyî",
"tmq": "tumleoyî",
"tgx": "tagişî",
"pbr": "pangwayî",
"loj": "louyî",
"emp": "emberayiya bakurî",
"nho": "takuuyî",
"bco": "kalulî",
"ifb": "batad ifugaoyî",
"jao": "yanyuwayî",
"inc-ohi": "hindiya kevn",
"cup": "cupeñoyî",
"bci": "bawuleyî",
"ngo": "ngonî",
"nun": "anongî",
"yee": "yimasî",
"kxb": "krobuyî",
"hub": "huambisayî",
"nee": "kumakî",
"bec": "iceve-macî",
"smb": "simbarî",
"kuj": "kuriayî",
"ian": "îatmulî",
"cjv": "çuaveyî",
"zne": "zandeyî",
"ssd": "siroî",
"cbn": "nyah kurî",
"lwg": "wangayî",
"bmr": "muinanî",
"kya": "kwayayî",
"xem": "kembayanî",
"acz": "açeronî",
"pav": "wari'",
"egx-dem": "demotîkî",
"bph": "botlixî",
"lev": "lammayî",
"btp": "budibudî",
"bzq": "bulî",
"tdv": "toroyî",
"klq": "rumuyî",
"ojv": "javayiya ontongê",
"bdi": "burunî",
"wya": "wyandotî",
"pbb": "paezî",
"lfn": "lingua franca nova",
"jit": "jitayî",
"amq": "amahaî",
"tcf": "mepaya malînaltepekî",
"ebu": "embuyî",
"rwm": "ambayî",
"pnh": "penrhynî",
"and": "ansus",
"pej": "pomoyiya bakurî",
"kue": "kumanî",
"fip": "fipayî",
"kcn": "nubiyî",
"lol": "mongoyî",
"bwd": "biwaydokayî",
"aia": "arosî",
"zau": "zangskarî",
"pud": "punan aputî",
"ikz": "îkizuyî",
"bgb": "bobongko",
"thk": "tharakayî",
"ymc": "mujiya başûrî",
"mny": "manyawayî",
"xwa": "kwazayî",
"wrs": "warisî",
"nai-tip": "tipaî",
"teu": "sooyî",
"ked": "kereweyî",
"szp": "suaboyî",
"rgr": "resigaroyî",
"bdm": "budumayî",
"nib": "nakameyî",
"lek": "leyponî",
"guh": "guahiboyî",
"bqr": "burusuyî",
"apl": "lipan",
"bnd": "banda",
"tsj": "tşanglayî",
"xme-old": "mediya kevn",
"ppn": "papapanayî",
"mwp": "kala lagaw yayî",
"bfa": "bariyî",
"plh": "paulohî",
"btd": "batakiya dairî",
"ure": "uruyî",
"sty": "tatariya sîberî",
"buy": "mmaniyî",
"xyy": "yorta yortayî",
"xdy": "malayîk dayakî",
"kmj": "kumarbhag pahariayî",
"vun": "vunjoyî",
"cuh": "çukayî",
"lgl": "walayî",
"pna": "punan bah-biauyî",
"now": "nyamboyî",
"gqa": "ga'andayî",
"thm": "thavungî",
"acr": "açiyî",
"cir": "tiriyî",
"mxr": "murîkî (Malezya)",
"swp": "suauyî",
"zor": "zoqueyiya rayón",
"wic": "wiçitayî",
"suw": "sumbwayî",
"bum": "buluyî (Kamerûn)",
"rnw": "rungwayî",
"nak": "nakanayî",
"trs": "trîkuyiya çicahuaxtla",
"lpo": "lipoyî",
"gwe": "gwenoyî",
"cbd": "carijonayî",
"mot": "barî",
"tlv": "taliabuyî",
"xsu": "sanumayî",
"sjl": "mijî",
"pip": "peroyî",
"acx": "erebiya ummanî",
"ybb": "yembayî",
"uln": "unserdeutsch",
"yil": "yindjilandjî",
"ykn": "kua-nsî",
"apu": "apurinã",
"sso": "sissanoyî",
"puc": "punan merapî",
"mwn": "nyamwangayî",
"bpa": "dakakayî",
"mmd": "maonanî",
"bof": "bolonî",
"ati": "attié",
"wji": "warjî",
"lra": "rara bakati'",
"mlu": "toabaîtayî",
"bdd": "bunamayî",
"bfd": "bafutî",
"keu": "akebuyî",
"wod": "wolanî",
"ksb": "şambalayî",
"mff": "nakî",
"azn": "durango nahuatliya rojavayî ya jêrîn",
"asi": "buruwayî",
"ala": "alago",
"tpy": "trumaî",
"zms": "mbesayî",
"kib": "koalibî",
"jun": "juangî",
"guf": "gupapuynguyî",
"liy": "banda-bambariyî",
"sai-pam": "pamiguayî",
"loa": "lolodayî",
"elm": "elemeyî",
"aad": "amalî",
"lcm": "tungagî",
"twh": "tai dónî",
"ppm": "papumayî",
"xmr": "meroytîkî",
"kno": "konoyî (sierra leone)",
"kzz": "kalabrî",
"emw": "emplawasî",
"pnk": "pawnakayî",
"gym": "ngäbereyî",
"mxj": "mijuyî",
"lun": "lundayî",
"kos": "kosraeyî",
"kzx": "kamarianî",
"aan": "anambeyî",
"ukq": "ukwayî",
"tau": "tananaya jorîn",
"anc": "ngas",
"rcf": "kreyola fransî ya réunion",
"knm": "kanamariyî",
"bvy": "baybayanonî",
"jnj": "yemsayî",
"onu": "unuayî",
"bdh": "tara bakayî",
"pij": "pijaoyî",
"mks": "mixteciya silacayoapan",
"juh": "honeyî",
"etx": "etenî",
"sai-chr": "çarruayî",
"aui": "anukî",
"cuc": "usila çinantecî",
"xme-ott": "tatiya kevn",
"njj": "njenî",
"mng": "mnongiya rojhilatî",
"cih": "çinaliyî",
"ard": "arabana",
"val": "vehesî",
"aos": "taykatî",
"peb": "pomoyiya rojhilatî",
"xcb": "kumbrîkî",
"clk": "iduyî",
"aem": "aremî",
"vut": "vuteyî",
"teq": "temeinî",
"siu": "sinagenî",
"xmm": "manado malayî",
"gdj": "kurtjarî",
"xau": "kauwerayî",
"atz": "arta",
"zsr": "zapoteciya rincona başûrî",
"wmt": "walmajarrî",
"ter": "terenoyî",
"sit-sit": "situyî",
"zns": "mangasî",
"kwz": "kwadiyî",
"iki": "îkoyî",
"prq": "aşeninka pereneyî",
"ztq": "zapotekiya quioquitani-quieri",
"wan": "wanî",
"txh": "thrasî",
"ahg": "qimant",
"boe": "mundablî",
"gww": "kwiniyî",
"ior": "înorî",
"ply": "bolyuyî",
"avt": "au",
"zpu": "zapotekiya yalalagî",
"bgv": "warkay-bîpîmî",
"srb": "sorayî",
"huu": "murui huitotoyî",
"zmm": "marimanindjî",
"xme-ker": "kermanî",
"gbv": "gbanuyî",
"awe": "awetí",
"xzh": "zhang-zhungî",
"wow": "wawonî",
"tqo": "toaripî",
"syb": "subaneniya navendî",
"ril": "riangî",
"nqo": "n'koyî",
"laj": "langoyî (Uganda)",
"bbh": "buganî",
"orr": "orumayî",
"gju": "gojriyî",
"ddd": "dongotonoyî",
"yik": "dongşanba laloyî",
"sai-bag": "baguayî",
"tsr": "akeî",
"txe": "totolî",
"nal": "nalikî",
"bez": "kîbenayî",
"jeh": "jehî",
"sem-saf": "safaitîkî",
"mka": "mbreyî",
"nai-jum": "jumaytepequeyî",
"ebg": "ebughuyî",
"cbv": "cacuayî",
"lnl": "bandayiya navendî ya başûr",
"inc-kho": "xolosî",
"tzx": "tabriakî",
"sln": "salînanî",
"ncr": "ncaneyî",
"kqt": "kadazaniya çemê kliasê",
"cbr": "caşibo-cacataiboyî",
"trt": "tunggareyî",
"tyj": "tai doyî",
"kpr": "korafe-yeghayî",
"hmt": "hamtaiyî",
"csi": "coast miwokî",
"bbi": "barombî",
"tvk": "ambrîmiya başûr-rojhilat",
"ynd": "yandruwandhayî",
"sai-sap": "saparayî",
"mik": "mikasukî",
"kid": "koşinî",
"bya": "batakiya palawan",
"awd-ana": "anauyayî",
"yrw": "yarawatayî",
"tnx": "tanemayî",
"lme": "peveyî",
"nbm": "ngbaka ma'boyî",
"myy": "macunayî",
"cao": "çacoboyî",
"anp": "angîka",
"sim": "seimî",
"nha": "nhandayî",
"kqn": "kaondeyî",
"ecr": "eteocretanî",
"crv": "çaurayî",
"bef": "benabenayî",
"aud": "anuta",
"yiy": "yir-yorontî",
"usp": "uspantekoyî",
"sry": "serayî",
"njy": "njyemî",
"mod": "mobilianî",
"mmt": "malalamaî",
"leu": "karayî (Gîneya nû)",
"mbe": "molaleyî",
"txi": "ikpengî",
"snc": "sinaugoroyî",
"noc": "nukî",
"mle": "manambuyî",
"kyr": "kuruayayî",
"ivb": "îbatanî",
"sai-chu": "çuruyayî",
"lsr": "aruopî",
"zaq": "zapotekiya aloapam",
"tef": "teressayî",
"siy": "sivandî",
"nyb": "nyangboyî",
"mpi": "mpadeyî",
"tcq": "kayî",
"kbt": "gabadiyî",
"dka": "dakpayî",
"bff": "bofî",
"ahs": "aşeyî",
"zar": "zapotekiya rincón",
"xct": "tibetiya klasîk",
"afz": "obokuîtayî",
"mnf": "mundanî",
"mpw": "mapidianî",
"gza": "ganzayî",
"acv": "açumawî",
"zmu": "muruwarî",
"zmt": "maringarrî",
"lbc": "lakkiayî",
"gbg": "gbanziriyî",
"des": "desanoyî",
"kud": "awhelawayî",
"agc": "agatuyî",
"tob": "tobayî",
"suc": "subanoniya rojavayî",
"oni": "onînî",
"mne": "nabayî",
"itm": "îtu mbon uzoyî",
"ges": "geser-goromî",
"aza": "azhayî",
"nfl": "aiwooyî",
"tqp": "tomoipî",
"rof": "romboyî",
"ury": "oryayî",
"axg": "mato grosso arára",
"jax": "jambî malayî",
"gkn": "gokanayî",
"tbq-gkh": "gokhyî",
"bkz": "bungku",
"aik": "akeyî",
"tku": "totonaciya nekaksaya jorîn",
"sho": "şangayî",
"crp-rsn": "russenorskî",
"nss": "nalî",
"guo": "guayaberoyî",
"dun": "dusun deyahî",
"bsm": "busamiyî",
"ble": "balanta-kentohe",
"axx": "xaragure",
"sxg": "şixingî",
"apz": "safeyoka",
"awd-pai": "paikonekayî",
"muc": "mbu'",
"kgu": "kobolî",
"dbf": "edopiyî",
"bbp": "bandayiya rojavayî ya navendî",
"loe": "saluanî",
"nux": "mehekî",
"elk": "elkeiyî",
"yaf": "yakayî",
"tiz": "tai hongjinî",
"stu": "samtaoyî",
"nkg": "nekginî",
"koz": "korakî",
"aus-bra": "barranbinyayî",
"tlr": "talîsî",
"spc": "sapeyî",
"sai-pyg": "payaguayî",
"tbq-mil": "milangî",
"klp": "kamasayî",
"crz": "cruzeñoyî",
"boj": "ancamî",
"tan": "tangaleyî",
"svs": "savosavoyî",
"ppq": "peyî",
"ngg": "ngbaka manzayî",
"mpx": "misima-paneatî",
"kcp": "kangayî",
"cbi": "çaçiyî",
"bfc": "bayiya bakurî",
"yec": "yeniçeyî",
"sai-trr": "tarairiú",
"plg": "pilagayî",
"hoe": "horomî",
"yun": "benayî",
"any": "anyî",
"yis": "yisî",
"ush": "uşojoyî",
"tav": "tatuyoyî",
"sve": "serilî",
"gob": "playeroyî",
"nkz": "nkarî",
"moc": "mocovî",
"jko": "kuboyî",
"reg": "karayî (Tanzanya)",
"hul": "hulayî",
"chh": "çinookî",
"yub": "yugambalî",
"tbg": "tayrorayiya bakur",
"mdc": "maleyî",
"kuo": "kumukioyî",
"mvy": "indus kohistanî",
"foi": "foiyî",
"coz": "çoçotecî",
"bim": "bimobayî",
"agf": "argunî",
"kmx": "wabodayî",
"mtl": "montolî",
"kjr": "kuruduyî",
"kcb": "kawaçayî",
"ibn": "îbinoyî",
"scb": "çutî",
"atx": "arutanî",
"mvt": "mpotovoroyî",
"kxa": "kayrîrûyî",
"boq": "bogayayî",
"zpx": "zapotekiya san baltazar loxiça",
"wnc": "wantoatî",
"itd": "tidongiya başûrî",
"xog": "sogayî",
"ann": "obolo",
"unr": "mundarî",
"bzh": "mapos buangî",
"tye": "kyengayî",
"kyq": "kengayî",
"kwa": "dâwî",
"bou": "bondeyî",
"aul": "aulua",
"zty": "zapotekiya yatee",
"wrp": "waropenî",
"mmi": "musarî",
"buq": "bremî",
"auj": "awjîla",
"gtu": "aghu tharrnggalayî",
"zil": "zialoyî",
"tlb": "tobeloyî",
"mty": "nabî",
"lbz": "lardilî",
"gdd": "gêdagêdî",
"mdx": "dizinî",
"bgt": "bugotûyî",
"tpc": "azoyú me'phaayî",
"jmb": "zumbunî",
"tdn": "tondanoyî",
"rng": "rongayî",
"mtm": "matorî",
"djd": "jaminjungî",
"gol": "golayî",
"doz": "dorzeyî",
"nmk": "namakurayî",
"mnt": "maykulanî",
"lbl": "libon bikolî",
"gde": "gudeyî",
"bxs": "busamî",
"xmd": "mbudumî",
"kdq": "koçî",
"cah": "cahuaranoyî",
"bza": "bandiyî",
"aes": "alseayî",
"dsq": "tadaksahakî",
"tmj": "samarokenayî",
"att": "pamplona atta",
"xhd": "hadramî",
"brp": "barapasiyî",
"omw": "tairorayiya başûr",
"nif": "nekî",
"iyo": "mesakayî",
"luq": "lucumiyî",
"kqz": "koranayî",
"coc": "cocopayî",
"amj": "amdang",
"tlk": "talokî",
"spi": "saponî",
"nnf": "ngaingî",
"mlj": "miltuyî",
"luz": "luriya başûrî",
"xas": "kamassî",
"cua": "cuayî",
"afd": "andaî",
"yxl": "yarlî",
"sit-tgm": "tangamî",
"nga": "ngbakayî",
"mrg": "misingî",
"igm": "kanggapeyî",
"gop": "yeretuarî",
"myh": "makahî",
"kpg": "kapîngamarancîyî",
"ero": "horpayî",
"eto": "etonî (Kamerûn)",
"bcq": "bençî",
"srr": "sererî",
"mqz": "malasangayî",
"hui": "huliyî",
"cap": "çipayayî",
"uar": "tayrumayî",
"mox": "molimayî",
"nai-ipa": "ipaî",
"arh": "arhuako",
"wdj": "wadjiginyî",
"tem": "temneyî",
"snn": "sionayî",
"mxk": "monumboyî",
"kru": "kurukhî",
"inc-mas": "asamiya navîn",
"zoo": "zapotekiya asunción mixtepec",
"wme": "wambuleyî",
"pru": "puragî",
"qwt": "kwalhioqua-tlatskanaî",
"gbb": "kaytetyeyî",
"bux": "boghomî",
"atm": "ata",
"abn": "abuayî",
"zab": "zapotekiya san juan guelavia",
"wny": "wanyî",
"sai-qtm": "quitemoyî",
"mmf": "mundatî",
"gdm": "laalî",
"kxi": "keningau murutî",
"dwr": "dawroyî",
"bfw": "bondoyî",
"aux": "aurá",
"ttv": "titanî",
"nrz": "lalayî (Gîneya nû)",
"gbp": "gbaya-bossangoayî",
"awn": "awngî",
"zia": "ziayî",
"way": "wayanayî",
"sut": "subtiabayî",
"rog": "roglayiya bakurî",
"ono": "onondagayî",
"mbj": "nadëbî",
"mls": "masalitî",
"bbj": "ghomalayî'",
"dyi": "djiminiyî",
"bgg": "bugunî",
"ail": "aîmeleyî",
"wir": "wirafedî",
"cla": "ronî",
"ost": "osatuyî",
"kkr": "kir-balarî",
"dgb": "bunogeyî",
"yha": "bahayî",
"mcd": "şaranahuayî",
"rwk": "rwayî",
"ifu": "mayoyao ifugaoyî",
"lig": "ligbiyî",
"jek": "jeri kuoyî",
"ghs": "guhu-samaneyî",
"bdn": "baldemuyî",
"xkf": "xengxayî",
"tsb": "tsamaî",
"sle": "şolagayî",
"nai-spt": "sahaptinî",
"nas": "nasioyî",
"mhd": "mbuguyî",
"kfa": "kodavayî",
"jur": "jurúnayî",
"goa": "guroyî",
"efa": "efaiyî",
"aqn": "altaya bakurî",
"tjs": "tujyayiya başûrî",
"tdy": "tadyawanî",
"los": "loniuyî",
"jka": "kaerayî",
"afo": "eloyî",
"cba-cat": "catio çibçayî",
"bsn": "barasanayî",
"ama": "amanayé",
"sai-san": "sanavironî",
"prc": "paraçî",
"nej": "nekoyî",
"cms": "messapicî",
"kyl": "kalapuyayî",
"coj": "coçimiyî",
"byr": "baruyayî",
"aey": "ameleyî",
"aus-ynk": "yangkaalî",
"yta": "taluyî",
"pay": "peç",
"lse": "lusengoyî",
"cub": "cubeoyî",
"gac": "andamaniya mezin a têkel",
"yry": "yarluyandî",
"tgp": "tangoayî",
"mjt": "sawriya pahariayî",
"phl": "phalurayî",
"ngt": "ngeqî",
"xnr": "kangrî",
"ciy": "çaimayî",
"bca": "bayiya navendî",
"aog": "angoram",
"yrn": "yerongî",
"skz": "sekarî",
"pia": "pima bajoyî",
"mjb": "makaleroyî",
"hot": "hoteyî",
"cbt": "çayahuitayî",
"aor": "aore",
"uve": "uveyiya rojava",
"pbv": "pnarî",
"mkr": "malasî",
"qvy": "kueyuyî",
"nai-chi": "çiquimulillayî",
"bhi": "bhilalî",
"erg": "sieyî",
"plo": "popolucayiya oluta",
"aun": "molmo one",
"kun": "kunamayî",
"laz": "aribwatsayî",
"waj": "waffayî",
"xsi": "sioyî",
"ibr": "îbuoroyî",
"zaf": "zapotekiya ayoquesco",
"lbq": "wamparî",
"keg": "teseyî",
"nyt": "nyawaygî",
"akc": "mpur",
"mnx": "manikionî",
"kmq": "kwamayî",
"khz": "keaparayî",
"ilk": "îlongotî",
"gaj": "gadsupî",
"aqp": "atakapa",
"kgr": "abunî",
"zpf": "zapotekiya san pedro quiatoni",
"sok": "sokoroyî",
"odu": "odualî",
"muh": "mündü",
"lbb": "labelî",
"knl": "keninjalî",
"bna": "boneratî",
"auy": "auyana",
"ade": "adeleyî",
"zpw": "zapotekiya zaniza",
"opm": "oksapminî",
"mug": "musguyî",
"hob": "mariyiya awistronezî",
"ywl": "laluyiya rojavayî",
"ksx": "kêdangî",
"gbq": "gbaya-bozoumî",
"xbm": "bretoniya navîn",
"kmn": "awtuwî",
"ziw": "zigulayî",
"wbm": "wayî",
"tgo": "sudestî",
"opo": "opaoyî",
"lan": "laruyî",
"xuu": "khweyî",
"gid": "gidarî",
"bvj": "baanî",
"zzj": "zuojiang zhuangî",
"wpc": "wirö",
"rnn": "roonî",
"don": "dourayî",
"bhj": "bahingî",
"lib": "likumî",
"akj": "jeru",
"awd-gnu": "guinauyî",
"dux": "duunî",
"bqp": "busayî",
"nsk": "naskapî",
"mqb": "mbukoyî",
"kwl": "kofyarî",
"nai-jtp": "jutiapayî",
"grd": "guruntumî",
"llp": "efateyiya bakur",
"agy": "altaya başûrî",
"yar": "yabaranayî",
"khc": "tukang besiya bakur",
"sbl": "sambaliya botolan",
"crc": "lonwolwolî",
"xkg": "kagoroyî",
"hdn": "haidayiya bakurî",
"sai-ctq": "catuquinaruyî",
"bbb": "barayî",
"amr": "amarakaerî",
"yky": "yakomayî",
"bsx": "yangkamî",
"tmz": "tamanakuyî",
"zsa": "sarasirayî",
"nig": "ngalakanî",
"khl": "lusiyî",
"xow": "kowakî",
"hem": "hembayî",
"pga": "erebiya jubayê",
"bhm": "batharî",
"bzb": "andioyî",
"yko": "yasayî",
"sax": "sayî",
"ypg": "pholayî",
"nnh": "ngiemboonî",
"mpd": "maçinereyî",
"vic": "kreyoliya giravên virginê",
"kdp": "kaningdon-nindemî",
"sit-cha": "çairelî",
"yes": "yeskwayî",
"uur": "urayî (Vanuatu)",
"mhu": "taraonî",
"slu": "selaruyî",
"xtd": "mixtekiya diuxi-tilantongo",
"utr": "etuloyî",
"bnv": "benerafî",
"ydk": "yoidikî",
"trr": "tauşîroyî",
"srk": "serudung murutî",
"xpo": "poçutekî",
"nkx": "nkorooyî",
"kes": "kugboyî",
"kgm": "karipúnayî",
"cax": "çiquitanoyî",
"big": "biangaî",
"vil": "vilelayî",
"dtu": "tebul ure dogonî",
"tty": "sikaritaî",
"rmg": "norweciya traveller",
"mlw": "molokoyî",
"biq": "bipî",
"ylu": "aribwaungî",
"wga": "wagayayî",
"tnz": "ten'ednî",
"qum": "sipakapenseyî",
"pue": "puelçeyî",
"nop": "numanggangî",
"mzq": "mori atasî",
"awd-kus": "kustenauyî",
"idi": "îdî",
"kzf": "da'a kailiyî",
"bib": "bîssayî",
"zad": "zapotekiya cajonos",
"waz": "wampurî",
"tll": "tetelayî",
"pur": "puruborayî",
"nih": "nyihayî",
"tnk": "kwamerayî",
"ilv": "îlueyî",
"dmn-dam": "damayî (Sierra leone)",
"cba-ata": "atanquesî",
"abu": "abureyî",
"zpt": "zapotekiya san vicente coatlan",
"aaw": "solongyî",
"opy": "ofayeyî",
"dtb": "labuk-kinabatangan kadazanî",
"ikt": "înuvialuktunî",
"auu": "auye",
"adi": "adî",
"wsk": "waskiayî",
"tix": "tiwayiya başûrî",
"atu": "reel",
"mui": "musî",
"iso": "îsokoyî",
"gya": "gbayayiya bakur-rojava",
"auw": "awyî",
"zik": "zimakanî",
"sai-way": "wayumarayî",
"sue": "suenayî",
"sai-opo": "opónî",
"srf": "nafî",
"qxs": "kiangiya başûrî",
"ggt": "gituayî",
"bcd": "babariya bakur",
"xam": "ǀxamî",
"wiu": "wiruyî",
"rro": "roroyî",
"osi": "osingî",
"mvb": "mattolî",
"mjk": "matukarî",
"lln": "leleyî (Çad)",
"kiy": "kirikiriyî",
"sit-jap": "japhugî",
"tds": "doutaî",
"blf": "buol",
"akt": "akolet",
"apb": "sa'a",
"awd-mpr": "maypureyî",
"jee": "jerungî",
"saj": "sahuyî",
"pkn": "pakanhayî",
"kkx": "kohinî",
"nai-cal": "calusayî",
"bgf": "banganduyî",
"ybm": "yabenî",
"bhq": "tukang besî başûr",
"mxe": "mele-filayî",
"lom": "loomayî",
"hka": "kaheyî",
"emn": "emanî",
"boi": "barbarenyoyî",
"aie": "amarayî",
"anh": "nendî",
"mej": "meyahî",
"kcl": "kalayî",
"hro": "haroiyî",
"enc": "enî",
"amy": "amî",
"yde": "yangum deyî",
"taj": "tamangiya rojhilatî",
"sar": "saravekayî",
"szb": "ngalumî",
"inm": "minaeanî",
"kwh": "kowyayî",
"hed": "herdeyî",
"cuy": "cuitlatecî",
"yvt": "yaviteroyî",
"sgw": "sebat bet gurageyî",
"nph": "phomî",
"mjs": "mişipî",
"mda": "madayî (Nîjerya)",
"wat": "kaninuwayî",
"blo": "anîî",
"yss": "yessan-mayoyî",
"ona": "selk'namî",
"pny": "pinyinî",
"noe": "nimadî",
"crm": "kriya mûsî",
"hhy": "hoyahoyayî",
"bzz": "evantî",
"inc-apa": "apabhramsayî",
"uuu": "uyî",
"seu": "serui-lautî",
"pbi": "podokoyî",
"mhc": "moçoyî",
"kzw": "kaririyî",
"faa": "fasuyî",
"sai-chi": "çirinoyî",
"yab": "yuhupî",
"vit": "vitî",
"myl": "momayî",
"crb": "island caribî",
"frq": "forakî",
"cog": "çongî",
"brk": "birgidî",
"waq": "wagemanî",
"nuy": "nunggubuyuyî",
"mdb": "morigî",
"idu": "îdomayî",
"fun": "fulniôî",
"dao": "daai çinî",
"wnb": "wanambreyî",
"skv": "skouyî",
"sai-pur": "purukotó",
"nyi": "nyimangî",
"kwn": "kwangaliyî",
"kec": "keigayî",
"gbu": "gagaduyî",
"dnj": "danî",
"bus": "bokobaruyî",
"ado": "abuyî",
"zpn": "zapotekiya santa ines yatzeçi",
"aml": "war-jaîntî",
"mij": "mungbamî",
"lbu": "labuyî",
"ken": "kenyangî",
"ipo": "îpikoyî",
"zam": "zapotekoyiya mahuatlana navendî",
"wac": "wasko-wişramî",
"sww": "sowayî",
"msu": "musomî",
"rkm": "markayî",
"slp": "lamaholotî",
"gso": "gbayayiya başûr-rojava",
"ahb": "axamb",
"kit": "agobî",
"bfi": "zimanê destan ê brîtanî",
"taz": "toçoyî",
"swi": "suî",
"prm": "kibirî",
"gis": "gizigayiya bakur",
"doe": "doeyî",
"abj": "aka-beayî",
"hnh": "ǁaniyî",
"roo": "rotokasî",
"nlv": "nahwatliya orizaba",
"npl": "nahwatliya pueblaya başûr-rojhilatî",
"llc": "leleyî (Gîne)",
"fkk": "kirya-konzelî",
"mde": "bura mabangî",
"bdl": "bajwayiya endonezyayî",
"bss": "akooseyî",
"scw": "şayî",
"lsm": "saamiayî",
"pqa": "pa'ayî",
"nmq": "nambyayî",
"mbq": "maysînî",
"jib": "jibuyî",
"bhs": "buwalî",
"sjo": "xibeyî",
"abw": "pal",
"nxa": "naueteyî",
"mck": "mbundayî",
"jmx": "juxtlahuaca mixteciya rojavayî",
"clu": "caluyanunî",
"ybo": "yabongî",
"tuo": "tukanoyî",
"nml": "ndemlî",
"mfx": "meloyî",
"lop": "lopayî",
"kzv": "komyandaretî",
"kct": "kaianî",
"has": "haislayî",
"mmw": "emayî",
"cyb": "cayubabayî",
"bcj": "bardî",
"awd-ama": "amarizanayî",
"ylr": "yalarnngayî",
"ago": "taînaeyî",
"prw": "parawenî",
"enw": "enwangî",
"kog": "coguiyî",
"bst": "basketoyî",
"ymo": "yangum monî",
"uda": "udayî",
"tma": "tamayî (Çad)",
"sre": "sarayî",
"nbv": "ngamamboyî",
"mcv": "minanibaî",
"koh": "koyoyî",
"zbt": "batuî",
"anb": "andoa",
"yaw": "yawalapitî",
"tnc": "tanimuca-retuarã",
"sbx": "seberuangî",
"yhl": "hlepho phowayî",
"bzv": "bebeyî",
"anf": "anîmere",
"yet": "yetfayî",
"pir": "piratapuyoyî",
"nmw": "nimoayî",
"nhb": "bengî",
"tbo": "tawalayî",
"szd": "seruyî",
"poy": "pogoloyî",
"bve": "malayiya berau",
"ctm": "çitimaçayî",
"aaf": "aranadanî",
"mkh-mvi": "viyetnamiya navîn",
"mnd": "mondeyî",
"mup": "malvî",
"kse": "kuniyî",
"btf": "birgîtî",
"aac": "ariyî",
"tex": "tennetî",
"kbb": "kaxuyanayî",
"ula": "fungwayî",
"dsh": "daasanaçî",
"zps": "zapotekiya coatlan",
"wnp": "wanapî",
"scs": "slaveyiya bakur",
"lid": "nyindrouyî",
"kvi": "kwangî",
"idb": "îndo-portegîzî",
"bdt": "bokotoyî",
"ate": "atemble",
"zpi": "zapotekiya santa maria quiegolani",
"tvy": "timor pidginî",
"sol": "solosî",
"rjs": "rajbanşî",
"ogc": "ogbahî",
"mth": "mungguî",
"dmc": "gavakî",
"zaj": "zaramoyî",
"sss": "soyî",
"nai-okw": "okwanuçuyî",
"hia": "lamangî",
"gyg": "gbayiyî",
"duy": "dicamay agtayî",
"sgt": "brokpakeyî",
"yix": "axi yî",
"tcx": "todayî",
"sui": "sukî",
"phi-nag": "nagtipunan agtayî",
"ixc": "îxcatecî",
"giz": "gizigayiya başûr",
"baa": "babatanayî",
"akm": "aka-bo",
"wiy": "wiyotî",
"tnt": "tontemboanî",
"uta": "otankî",
"nhc": "nahwatliya tabasco",
"mph": "maungî",
"led": "lenduyî",
"kiz": "kisiyî",
"dva": "duauyî",
"bvr": "burarrayî",
"yig": "wusa nasuyî",
"neb": "tourayî",
"shv": "şehrî",
"nnm": "namiayî",
"nai-wji": "jicaqueyiya rojavayî",
"gve": "duwetî",
"yln": "buyangiya langnî",
"blq": "baluan-pam",
"jiv": "şuarî",
"quv": "sakapultekoyî",
"ynk": "naukanskî",
"mgy": "mbungayî",
"kpx": "koiariyiya çiyayî",
"ktz": "juǀ'hoanî",
"ofu": "efutopî",
"wes": "cameroon pidginî",
"dba": "bangi meyî",
"aab": "alumu-tesuyî",
"bkj": "pande",
"ndg": "ndengerekoyî",
"bbo": "konabereyî",
"zka": "kaimbulawayî",
"hgm": "haiǁomî",
"alv-ama": "amasî",
"ttm": "tutçoneyiya bakurî",
"nen": "nengoneyî",
"zmf": "mfinuyî",
"ruf": "luguruyî",
"kfq": "korkuyî",
"tmd": "haruaî",
"eot": "eotileyî",
"sem-amm": "ammoniteyî",
"tdk": "tambasî",
"sba": "ngambayî",
"lue": "luvaleyî",
"khq": "koyra çiiniyî",
"doc": "kamiya bakurî",
"sai-cul": "cullî",
"ano": "andoque",
"yww": "yawarawargayî",
"uji": "tanjijilî",
"ypz": "phuzayî",
"nsq": "miwokiya sierraya bakurî",
"sgb": "mag-ançî aytayî",
"ish": "esanî",
"aoi": "anîndîlyakwa",
"uri": "urimî",
"yer": "tarokî",
"shb": "ninamî",
"kpj": "karajayî",
"hoz": "hozoyî",
"twm": "tawang monpayî",
"mqo": "modoleyî",
"meo": "malayiya kedah",
"bbv": "karnaî",
"agl": "fembeyî",
"ctg": "çittagonianî",
"tkq": "teeyî",
"wmd": "mamaindeyî",
"kjn": "kunjenî",
"kmg": "kâteyî",
"bqq": "biritaiyî",
"aai": "arîfama-mînîafîayî",
"swj": "sirayî",
"puw": "puluwatî",
"rin": "nunguyî",
"bdo": "moromî",
"khj": "kuturmiyî",
"fvr": "furî",
"dbq": "dabayî",
"blr": "blang",
"mfi": "wandalayî",
"xsl": "slaveyiya başûr",
"pww": "pwoyiya bakurî",
"knp": "kwanjayî",
"gbi": "galelayî",
"bqc": "bokoyî",
"zpr": "zapotekiya santiago xanica",
"sai-tim": "timoteyî",
"rki": "rakhineyî",
"ogb": "ogbiayî",
"mpv": "mungkipî",
"ker": "kerayî",
"iqu": "îquitoyî",
"gou": "gavarî",
"bvf": "boorî",
"tmb": "avavayî",
"was": "waşoyî",
"tkp": "tîkopyayî",
"reb": "rembongî",
"ogo": "khanayî",
"diu": "gcirikuyî",
"orw": "oro wînî",
"nlx": "nahalî",
"vme": "maselayiya rojhilat",
"ltn": "latundê",
"bvx": "baboleyî",
"aci": "aka-karî",
"rug": "rovyanayî",
"kqs": "kissiya bakurî",
"dmu": "dubuyî",
"bwr": "burayî",
"bkc": "baka",
"akf": "akpayî",
"wut": "wutungî",
"she": "şekoyî",
"pma": "paamayî",
"mgi": "jilî",
"gvl": "gulayî",
"dyy": "dyaabugayî",
"aiy": "aliyî",
"xin": "xincayî",
"skf": "sakirabiayî",
"zmp": "mbuunî",
"bex": "jûr modoyî",
"mea": "menkayî",
"kfc": "konda-dorayî",
"kbw": "kaîepî",
"cto": "embera-catioyî",
"cam": "cemuhî",
"bae": "baré",
"tce": "tutçoneyiya başûrî",
"nfu": "mfumteyî",
"lcq": "luhuyî",
"oco": "korniya kevn",
"ktg": "kalkatunguyî",
"sja": "epenayî",
"col": "columbia-wenatçiyî",
"alx": "amol",
"wca": "yanomamî",
"xxt": "tamborayî",
"auk": "heyo",
"cuo": "cumanagotoyî",
"sai-and": "andoqueroyî",
"yva": "yawayî",
"due": "umiray dumaget agtayî",
"kml": "tanudan kalingayî",
"ptr": "piamatsinayî",
"blx": "mag-îndî ayta",
"gcl": "kreyoliya îngilîzî ya grenadî",
"ese": "ese ejjayî",
"uvh": "urî",
"nsm": "semayî",
"nin": "ninzoyî",
"hra": "hrangkholî",
"nai-taw": "tawasayî",
"sbi": "setî",
"mvq": "moereyî",
"mqg": "malayiya kota bangun kutai",
"uwa": "kuku-uwanhî",
"iow": "çiwereyî",
"bid": "bidiyoyî",
"yuk": "yukî",
"teh": "tehuelçeyî",
"mkc": "siliputî",
"zle-ono": "novgorodiya kevn",
"wmh": "waima'ayî",
"tio": "teopî",
"yae": "pumeyî",
"mqn": "moroneneyî",
"zma": "mandayî (Awistralya)",
"kdc": "kutuyî",
"kyt": "kayagarî",
"mps": "dadibî",
"byn": "blinî",
"cns": "asmatiya navendî",
"utp": "abayî",
"zpc": "zapotekiya çoapan",
"den": "slaveyî",
"pwo": "pwoyiya rojavayî",
"mub": "mubî",
"mev": "manoyî",
"bij": "kwankayî",
"kyo": "kelonî",
"inb": "îngayî",
"aph": "athpare",
"zas": "zapotekiya santo domingo albarradas",
"ral": "ralteyî",
"irn": "îrantxeyî",
"dge": "degenanî",
"boa": "borayî",
"adj": "adyukruyî",
"zal": "zauzouyî",
"wax": "watamî",
"tca": "tîkunayî",
"agv": "remontado agta",
"omg": "omaguayî",
"xru": "marriammuyî",
"khn": "khandeşiyî",
"gji": "gejiyî",
"ayd": "ayabadhu",
"zmb": "zimbayî",
"toj": "tojolabalî",
"syk": "sukurî",
"bnf": "masiwang",
"glw": "glavdayî",
"bav": "babungoyî",
"akx": "aka-kede",
"ots": "otomiya estado de meksîka",
"mjj": "mawakî",
"leh": "lenjeyî",
"kss": "kissiyiya başûrî",
"kbz": "duhwayî",
"sth": "şeltayî",
"myf": "bambassî",
"ald": "alladînî",
"ego": "eggonî",
"cbu": "candoşi-şaprayî",
"tmc": "tumakî",
"pgs": "pangsengî",
"ndz": "ndogoyî",
"knd": "kondayî",
"haj": "hajongî",
"cmi": "embera-çamiyî",
"mlp": "bargamî",
"yat": "yambetayî",
"bsg": "başkardiyî",
"pek": "pençalî",
"lwo": "luwoyî",
"kpk": "kpanî",
"kdx": "kamî",
"cug": "cungî",
"anx": "andra-hus",
"sey": "secoyayî",
"pio": "piapocoyî",
"jgo": "ngombayî",
"miy": "mixteciya ayutla",
"gyn": "kreyoliya îngilîzî ya guyanî",
"hoa": "hoavayî",
"amx": "anmatyerre",
"tdm": "tarumayî",
"sza": "semelayî",
"pit": "pitta-pittayî",
"nsz": "nisenanî",
"mis-mmd": "mîmiya decorseyî",
"mky": "makianiya rojhilat",
"kdj": "karamojongî",
"hre": "hreyî",
"bie": "bepourî",
"ena": "apaliyî",
"vgr": "vaghrî",
"sze": "sezeyî",
"not": "nomatsiguengayî",
"mif": "mofu-gudurî",
"mfp": "malayiya makassar",
"kul": "kulereyî",
"huo": "huyî",
"biu": "biete",
"twe": "teiwayî",
"stv": "silt'eyî",
"mqj": "mamasayî",
"kug": "kupayî",
"chw": "çuaboyî",
"zkr": "zakhringî",
"waw": "waiwaî",
"ntp": "tepehuaniya bakurî",
"pmi": "pumiya bakurî",
"hux": "nüpode huitotoyî",
"mor": "moroyî",
"mha": "mandayî (Hindistan)",
"thd": "kuuk thaayorreyî",
"dgz": "dagayî",
"mae": "bo-rukulî",
"sjg": "assangorî",
"zpg": "zapotekiya guevea de humboldt",
"tcz": "thado çinî",
"nyv": "nyulnyulî",
"aso": "dano",
"bld": "bolango",
"snk": "soninkeyî",
"sai-mar": "maratinoyî",
"laf": "lafofayî",
"krk": "kerekî",
"gyl": "gayilî",
"ksr": "borongî",
"avi": "avîkam",
"wah": "watubelayî",
"tpl": "tlacoapa me'phaayî",
"mfr": "marrithiyelî",
"khr": "khariayî",
"giq": "gelaoyiya hêşîn",
"aye": "ayere",
"owl": "weylsiya kevn",
"zsu": "sukurumî",
"nhk": "nahwatliya cosoleacaque",
"law": "laujeyî",
"izr": "îzereyî",
"goi": "gobasiyî",
"wog": "wogamusinî",
"ttu": "torauyî",
"dts": "toro so dogonî",
"mcw": "mawayî",
"azc-ktn": "kitanemukî",
"ddj": "jaruyî",
"dma": "dumayî",
"bkh": "bakoko",
"aqz": "akuntsu",
"pdo": "padoeyî",
"nnb": "nandeyî",
"dzl": "dzalayî",
"bax": "bamumî",
"xiy": "xipayayî",
"tct": "t'enî",
"lht": "lo-togayî",
"kkk": "kokotayî",
"lot": "lotukoyî",
"tdc": "embera-tadó",
"bjc": "barijî",
"aew": "ambakîç",
"tkm": "takelmayî",
"sbp": "sanguyî (Tanzanya)",
"sai-ptg": "patagónî",
"lgk": "neververî",
"ybj": "haşayî",
"amg": "amurdag",
"yns": "yansî",
"udu": "udukî",
"mwm": "sarî",
"ums": "pendauyî",
"wii": "minidienî",
"pbn": "kpasamî",
"cuq": "cunî",
"bbd": "bauyî",
"aty": "aneityum",
"ngc": "ngombeyî (Kongo)",
"kxo": "kanoeyî",
"ags": "esîmbî",
"bxb": "belanda borî",
"urn": "uruangnirinî",
"mis-mmn": "mîmiya naçtigalî",
"hru": "hrusoyî",
"apt": "apatanî",
"yon": "yongkomî",
"vag": "vaglayî",
"vkl": "kulisusuyî",
"kxh": "karoyî",
"jab": "hyamî",
"smg": "simbalî",
"asj": "nsarî",
"knk": "kurankoyî",
"kaf": "katsoyî",
"yml": "îamaleleyî",
"brt": "bitareyî",
"bao": "waîmaha",
"tla": "tepehuaniya başûr-rojavayî",
"pmj": "pumiya başûrî",
"nuf": "nusuyî",
"mrl": "mortlockeseyî",
"man": "mandingoyî",
"ign": "îgnacianoyî",
"bzn": "boanoyî (Maluku)",
"xss": "assanî",
"wne": "wanecî",
"thf": "thangmî",
"kjb": "q'anjob'alî",
"sai-muc": "mucuçî",
"msw": "mansoankayî",
"kem": "kemakî",
"bnp": "bolayî",
"aqm": "atohwaim",
"zat": "zapotekiya tabaa",
"sov": "sonsorolî",
"lhn": "lahananî",
"kjq": "keresiya rojavayî",
"dei": "demisayî",
"kwi": "awa-cuaiquerî",
"aez": "aekayî",
"zay": "zayse-zergullayî",
"una": "watutiya bakur",
"ctl": "tlacoatzintepec çinantecî",
"squ": "squamîşî",
"omb": "ombayî",
"sur": "mwaghavulî",
"umc": "marrusînî",
"lbn": "lametî",
"phi-din": "dinapigue agtayî",
"buw": "bubiyî",
"ahn": "ahàn",
"zrn": "zirenkelî",
"srg": "sulodî",
"ora": "orohayî",
"mtn": "matagalpayî",
"kco": "kinalaknayî",
"hmd": "a-hmaoyî",
"woc": "wogeoyî",
"tqr": "toronayî",
"rbb": "rumaî palaungî",
"ott": "otomiya temoaya",
"nhi": "nahwatliya zacatlan-ahuacatlan-tepetzintla",
"mgk": "mawesî",
"dus": "dumiyî",
"bzu": "burmesoyî",
"bbu": "bakulungî",
"trn": "trinitarioyî",
"sdp": "şerdukpenî",
"sbv": "sabineyî",
"sai-jrj": "jirajirayî",
"gvs": "gumawanayî",
"dnn": "dzuunî",
"tte": "bwanabwanayî",
"bcf": "bamuyî",
"xir": "xiriânayî",
"two": "tswapongî",
"sai-tab": "tabancaleyî",
"lgq": "logbayî",
"klm": "kolomî",
"ngs": "gvokoyî",
"eka": "ekajukî",
"dbw": "bankan tey dogonî",
"aly": "alyawarr",
"yad": "yaguayî",
"ndp": "ndoyî",
"mcr": "menyayî",
"xon": "konkombayî",
"kka": "kakandayî",
"hao": "haköyî",
"ckl": "cibakî",
"jbk": "barikewayî",
"abc": "ambala ayta",
"yaa": "yaminahuayî",
"sny": "saniyo-hiyeweyî",
"mmc": "miçoacan mazahuayî",
"xkq": "koronî",
"had": "hatamî",
"sai-cop": "copallenî",
"swy": "saruayî",
"nid": "ngandî",
"mhi": "ma'dî",
"cpo": "kpeeyî",
"anz": "anem",
"unz": "unde kailî",
"sai-tpr": "taparitayî",
"sed": "sedangî",
"fpe": "piçinglisî",
"mxy": "mixteciya noçixtlana başûr-rojhilatî",
"bfz": "mahasu paharî",
"sai-esm": "esmeraldayî",
"urv": "uruavayî",
"seh": "senayî",
"spx": "pîkeneyiya başûr",
"cag": "nivacleyî",
"zkk": "karankawayî",
"bkl": "berik",
"yut": "yopnoyî",
"vai": "vaî",
"slw": "sialumî",
"noj": "nonuyayî",
"caf": "karrieriya başûrî",
"fag": "finonganî",
"ibh": "bihî",
"awt": "araweté",
"tyx": "teke-tyeeyî",
"mog": "mongondowî",
"knt": "katukinayî",
"bwm": "biwatî",
"apr": "arop-lokep",
"atr": "waîmîrî-atroarî",
"mtt": "motayî",
"pym": "fyamî",
"dlk": "dahalikî",
"bzl": "boanoyî (sulawesî)",
"abp": "abenlen ayta",
"zaw": "zapotekiya mitla",
"svm": "slavomolisanoyî",
"zpz": "zapotekiya texmelucan",
"tit": "tiniguayî",
"rax": "rangî",
"asx": "muratayak",
"mvr": "marauyî",
"lha": "lahayî (Viyetnam)",
"irh": "îrarûtûyî",
"sai-gay": "gayónî",
"awi": "aekyom",
"mcy": "watutiya başûr",
"tpt": "tlaçiçilco tepehuayî",
"sto": "stoneyî",
"rea": "rerauyî",
"aom": "ömie",
"tlx": "khehekî",
"mrx": "dineorî",
"oro": "orokoloyî",
"acl": "akar-baleyî",
"wbw": "woî",
"otn": "otomiya tenango",
"gia": "kitjayî",
"ats": "gros ventre",
"tri": "trió",
"zmz": "mbandjayî",
"yzg": "e'ma buyangî",
"bwa": "bwatûyî",
"bcw": "banayî",
"ywt": "xişanba laloyî",
"tsc": "tswayî",
"gmw-tsx": "saksoniya transylvanyayê",
"fap": "palorî",
"nwa": "nawathinehenayî",
"mue": "media lenguayî",
"kcx": "kaçama-ganjuleyî",
"dah": "gwahatikeyî",
"ekg": "ekariyî",
"cbc": "carapanayî",
"bxw": "bankayî",
"ygw": "yagwoiayî",
"tbr": "tumtumî",
"tgw": "tagwana senoufoyî",
"pnr": "panimî",
"njt": "ndyuka-trio pidginî",
"vnk": "lovonoyî",
"kma": "konniyî",
"hal": "halangî",
"awc": "cicipu",
"bch": "bariyayî",
"gfk": "patpatarî",
"ule": "luleyî",
"vkm": "kamakanî",
"xht": "hattîkî",
"btz": "batakiya alas-kluet",
"dti": "ana tinga dogonî",
"yre": "yaoureyî",
"udj": "ujirî",
"tpu": "tampuanî",
"ung": "ngarinyinî",
"xjb": "minjungbalî",
"slz": "ma'yayî",
"kpe": "kpelleyî",
"xla": "kamulayî",
"kpc": "curripacoyî",
"bvz": "bawziyî",
"ets": "yekheeyî",
"bbn": "uneapayî",
"tvw": "sedoayî",
"crp-spp": "pidginiya samoan plantation",
"ngq": "ngoremeyî",
"mil": "mixteciya peñoles",
"sai-chn": "çanayî",
"kvu": "yinbaw karenî",
"txx": "tatanayî",
"lva": "maku'ayî",
"gel": "fakkanciyî",
"bom": "beromî",
"cvn": "valle nacional çinantecî",
"sya": "siangî",
"bql": "bilakurayî",
"arv": "arbore",
"sai-yup": "yupuayî",
"vnp": "vunapuyî",
"sbb": "simboyî",
"gmw-pro": "proto-cermeniya rojava",
"nbn": "kurî",
"wbv": "wajarrî",
"mwh": "moûk-arîakî",
"bwp": "mandobo bawahî",
"fie": "fyerî",
"dal": "dahaloyî",
"aba": "abeyî",
"zpm": "zapotekiya mixtepec",
"wky": "wangkayutyuruyî",
"laq": "qabiaoyî",
"nja": "nzanyî",
"dmo": "kemezungî",
"dro": "daro-matu melanauyî",
"ato": "atong (Kamerun)",
"yif": "açeyî",
"zts": "zapotekiya tilquiapan",
"lah": "lahndayî",
"iry": "îrayayî",
"roa-bbn": "bourbonnais-berriçonî",
"aal": "afadeyî",
"czn": "zenzontepekiya çatîno",
"mpl": "watutiya navîn",
"lmg": "lamogayî",
"nlg": "gelayî",
"ayo": "ayoreoyî",
"zoh": "zoqueyiya çimalapa",
"mtp": "wiçi lhamtes noctenî",
"suv": "sulungî",
"xmt": "matbatî",
"zga": "kingayî",
"jbt": "jabutiyî",
"gof": "gofayî",
"rmt": "domarî",
"bwu": "bulî (Gana)",
"jod": "wojenakayî",
"yyu": "torricelli yauyî",
"sbf": "şaboyî",
"yan": "mayangnayî",
"kii": "kitsaiyî",
"trc": "trîkuyiya copala",
"shx": "şeyî",
"pgu": "paguyî",
"gwa": "mbatoyî",
"dyo": "jola-fonyiyî",
"yas": "gunuyî",
"igb": "ebirayî",
"plz": "paluanî",
"alv-gwa": "gwarayî",
"eke": "ekitî",
"cfd": "carayî",
"abb": "bankonî",
"amc": "amahuaca",
"rhp": "yahangî",
"lmb": "mereiyî",
"koa": "konomalayî",
"hlb": "halbiyî",
"eme": "emerillonî",
"nai-cig": "ciguayoyî",
"ael": "ambeleyî",
"jay": "yan-nhanguyî",
"tak": "talayî",
"mmy": "migaamayî",
"lmd": "lumunî",
"kfp": "korwayî",
"woi": "kamangî",
"akg": "anakalunguyî",
"nai-per": "pericú",
"nrl": "ngarlumayî",
"mew": "maakayî",
"hig": "kamweyî",
"hnd": "hindkoyiya başûrî",
"cuv": "cuvokî",
"ylg": "yeloguyî",
"une": "unemeyî",
"tpj": "tapieteyî",
"sos": "seenkuyî",
"nsh": "ngoşieyî",
"mit": "mixteciya pueblaya başûr",
"mxx": "mahouyî",
"bzk": "kreyoliya nîkaraguayî",
"esq": "esselenî",
"nbc": "çangî",
"anu": "anuak",
"snu": "senggî",
"mpu": "makurapî",
"hbb": "hubayî",
"cbg": "çimilayî",
"wti": "bertayî",
"app": "apma",
"spg": "sianî",
"snf": "noonî",
"mkp": "moikodî",
"viv": "idunayî",
"gem-pro": "proto-cermenî",
"omx": "moniya kevn",
"maw": "mamprulî",
"are": "arrernteya rojavayî",
"zah": "zangwalî",
"ssl": "sisaalayiya rojavayî",
"mpc": "mangarayî",
"qun": "kuynaultî",
"gyd": "kayardildî",
"ikx": "îkî",
"dij": "daiyî",
"bwq": "boboyiya başûrî",
"aum": "asu",
"axb": "abipon",
"zac": "zapotekiya ocotlan",
"xwk": "wangkumarayî",
"nzi": "nzimayî",
"mzv": "manzayî",
"kwe": "kwerbayî",
"sjk": "kemi samî",
"gmv": "gamoyî",
"bkt": "bolokî",
"atw": "atsugewî",
"zpk": "zapotekiya tlacolulita",
"wbe": "waritaî",
"ray": "rapayî",
"ojs": "ojibweyiya severnê",
"zmd": "maridanî",
"kxz": "kerewoyî",
"irr": "îrî",
"gbr": "gbagyiyî",
"bzx": "bozoyiya hainyaxo",
"aya": "awar",
"res": "reşeyî",
"oin": "oneyiya inebu",
"mwe": "mwerayî",
"ite": "îteneyî",
"gaf": "gendeyî",
"gdl": "diraşayî",
"nfd": "ahwayî",
"tlm": "tolomakoyî",
"mgv": "matengoyî",
"gqn": "kinikinaoyî",
"jah": "jah hutî",
"gog": "gogoyî",
"doy": "dompoyî",
"bfj": "bafanjî",
"xwc": "wokkonî",
"tei": "torricellî",
"sha": "şall-zwallî",
"stn": "owayî",
"emy": "mayaniya epigrafîk",
"azd": "durango nahuatliya rojavayî",
"bls": "balaesang",
"dul": "alabat island agtayî",
"xan": "xamtangayî",
"plk": "şinayiya kohistanî",
"sai-sac": "sacatayî",
"apf": "pahanan agta",
"npa": "nar phuyî",
"mfn": "mbembeyiya çema crossê",
"czk": "knaanicî",
"jow": "jowuluyî",
"cbb": "cabiyariyî",
"bpq": "banda malayî",
"sit-tsh": "tşobdunî",
"sbe": "salibayî (Gîneya nû)",
"mfj": "mefeleyî",
"lok": "lokoyî",
"kdu": "kadaruyî",
"ekp": "ekpeyeyî",
"bap": "bantawa",
"ali": "amaimonî",
"ynu": "yahunayî",
"tuf": "tuneboyiya navendî",
"ppu": "paporayî",
"dcr": "negerhollandsî",
"meq": "mereyî",
"nai-knm": "konomihuyî",
"ijn": "kalabariyî",
"hla": "haliayî",
"alm": "amblongî",
"ynn": "yanayî",
"ntj": "ngaanyatjarrayî",
"kbq": "kamanoyî",
"anm": "analî",
"ukg": "ukurigumayî",
"mav": "satere-maweyî",
"mfz": "mabaanî",
"kqq": "krenakî",
"hns": "hindustaniyiya karibî",
"taf": "tapirapeyî",
"mig": "mixteciya san miguel el grande",
"kni": "kanufiyî",
"sai-chp": "çapacurayî",
"cko": "anufoyî",
"wnu": "usanî",
"spe": "sepayî (Gîneya nû)",
"xrw": "karawayî",
"bnt-cmw": "çimwiinî",
"vam": "vanimoyî",
"sjr": "siar-lakî",
"poo": "pomoyiya navendî",
"mlz": "malaynonî",
"xks": "kumbewahayî",
"ldb": "idunî",
"tqb": "tembeyî",
"nai-sin": "sinacantanî",
"nur": "nuguriayî",
"mqx": "mamujuyî",
"unn": "kurnayî",
"bcn": "bîbaalî",
"ttr": "terayî",
"wkd": "moyî",
"kzl": "kayeliyî",
"ntk": "îkomayî",
"gdg": "ga'dangî",
"gbj": "bodo gadabayî",
"abi": "abîdjî",
"zao": "zapotekiya ozolotepec",
"sai-wnm": "wanhamî",
"thp": "thompsonî",
"cwe": "kwereyî",
"xbn": "kenaboî",
"gnq": "ganayî",
"tcc": "datoogayî",
"ztg": "zapotekiya xanaguia",
"zmj": "maridjabinî",
"lak": "lakayî",
"kvr": "kerînçî",
"ggu": "gbanî",
"anv": "denya",
"aof": "bragat",
"awr": "awera",
"zhb": "zhabayî",
"www": "wawayî",
"stb": "subaneniya bakurî",
"ret": "rettayî",
"oon": "öngeyî",
"mlv": "mwotlapî",
"mzr": "marúboyî",
"lag": "langiyî",
"kxc": "khonsoyî",
"itx": "îtikî",
"bja": "budza",
"ayu": "ayuyî",
"tba": "aikanayî",
"txm": "tominî",
"orx": "oroyî",
"nhq": "nahwatliya huaxcaleca",
"mpq": "matisî",
"kfk": "kinnauriyî",
"kzh": "dongolawiyî",
"bck": "bunabayî",
"ksf": "bafiayî"
},
"numberLabels": {
"y": {
"display": "yekjimar"
},
"d": {
"display": "hejmar dual"
},
"p": {
"display": "pirjimar"
},
"p+": {
"display": "axlebî pirjimar"
},
"tp": {
"display": "tenê pirjimar"
},
"j": {
"display": "jimarbar"
},
"j+": {
"display": "axlebî jimarbar"
},
"j/nj": {
"display": "jimarbar û nejimarbar"
},
"nj": {
"display": "nejimarbar"
},
"nj+": {
"display": "axlebî nejimarbar"
},
"hj!": {
"display": "hejmar nepeyitandî ye"
},
"hj?": {
"display": "hejmar hewce ye"
}
}
}
oggg72mqldg6f84hnyzyp2zytxoyl1l
Bikarhêner:Wikihez/Gadget-test/util.js
2
1358591
6334321
6334304
2026-08-17T13:50:56Z
Wikihez
7344
6334321
javascript
text/javascript
/**
* Generic text and UI helpers for the entry-creation wizard; no wizard-specific
* state, so any gadget can require("./start/util.js").
*/
//<nowiki>
function capitalize(str) {
if (!str) return str;
return str[0].toUpperCase() + str.substr(1);
}
function decapitalize(str) {
if (!str) return str;
return str[0].toLowerCase() + str.substr(1);
}
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function stripWikilinks(str) {
return str.replace(/\[\[([^\]|]+)(?:\|([^\]]+))?\]\]/g, function (m, target, display) {
return display || target;
});
}
function splitTercimeIzah(mane) {
var m = /^([\s\S]*?)\s*\{\{îzah\|([\s\S]*)\}\}\s*$/.exec(mane || "");
return m ? { tercime: m[1], izah: m[2] } : null;
}
function insertAtCursor(el, text) {
var start = el.selectionStart;
var end = el.selectionEnd;
var val = el.value;
el.value = val.slice(0, start) + text + val.slice(end);
var pos = start + text.length;
el.selectionStart = el.selectionEnd = pos;
}
var FOLD_EXTRA ={
"ı":"i", "ø":"o","đ":"d","ð":"d","ə":"e","ħ":"h","ł":"l","ŋ":"n","ʉ":"u", "ɨ":"i","ʒ":"z","ɛ":"e","ɔ":"o","æ":"ae", "œ":"oe","ß":"ss","þ":"th"
};
function foldDiacritics(str) {
if (str === null || str === undefined) return "";
var s = String(str).toLowerCase();
if (typeof s.normalize === "function") {
// Split "î" into "i" + combining circumflex, then drop the accent.
s = s.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
}
return s.replace(/[^\u0000-\u007f]/g, function (ch) {
return Object.prototype.hasOwnProperty.call(FOLD_EXTRA, ch) ? FOLD_EXTRA[ch] : ch;
});
}
var WIKTKU_RESIZE_STORAGE_PREFIX = "wiktku_resize_height_";
function wiktkuResizeStorageKey(textarea) {
var cls = (textarea.attr("class") || "").trim().replace(/\s+/g, "_");
return WIKTKU_RESIZE_STORAGE_PREFIX + cls;
}
function wiktkuSaveResizeHeight(textarea, height) {
if (!(window.mw && mw.storage)) return;
try {
mw.storage.set(wiktkuResizeStorageKey(textarea), String(Math.round(height)));
} catch (e) { /* localStorage unavailable (private mode, quota, …) — skip caching */ }
}
function wiktkuLoadResizeHeight(textarea) {
if (!(window.mw && mw.storage)) return null;
try {
var v = mw.storage.get(wiktkuResizeStorageKey(textarea));
return v ? parseFloat(v) : null;
} catch (e) {
return null;
}
}
function wiktkuMakeManualResizable(textarea) {
textarea = $(textarea);
if (!textarea.is("textarea") || textarea.hasClass("wiktku-start-narrow")) return;
if (textarea.data("wiktkuResizableDone")) return;
textarea.data("wiktkuResizableDone", true);
textarea.wrap("<div class='wiktku-resize-wrap'></div>");
var grip = $("<div class='wiktku-resize-grip'></div>");
textarea.after(grip);
// Restore this field's last saved height, if any.
var cachedHeight = wiktkuLoadResizeHeight(textarea);
if (cachedHeight) {
var minHeightInit = parseFloat(textarea.css("min-height")) || 0;
textarea.css("height", Math.max(cachedHeight, minHeightInit) + "px");
}
var dragging = false, startY = 0, startHeight = 0;
function onPointerMove(e) {
if (!dragging) return;
var oe = e.originalEvent || e;
var minHeight = parseFloat(textarea.css("min-height")) || 0;
var newHeight = startHeight + (oe.clientY - startY);
textarea.css("height", Math.max(newHeight, minHeight) + "px");
}
function onPointerUp(e) {
if (!dragging) return;
dragging = false;
var oe = e.originalEvent || e;
try { grip[0].releasePointerCapture(oe.pointerId); } catch (err) { /* ignore */ }
$(document).off("pointermove", onPointerMove);
$(document).off("pointerup pointercancel", onPointerUp);
wiktkuSaveResizeHeight(textarea, textarea.outerHeight());
}
grip.on("pointerdown", function (e) {
var oe = e.originalEvent || e;
if (oe.button !== undefined && oe.button !== 0) return; // primary contact only
e.preventDefault();
dragging = true;
startY = oe.clientY;
startHeight = textarea.outerHeight();
try { grip[0].setPointerCapture(oe.pointerId); } catch (err) { /* ignore */ }
$(document).on("pointermove", onPointerMove);
$(document).on("pointerup pointercancel", onPointerUp);
});
}
/** Applies wiktkuMakeManualResizable to every eligible textarea in `context` (itself or its descendants). */
function wiktkuInitManualResize(context) {
var $context = $(context);
var $textareas = $context.is("textarea") ? $context : $context.find("textarea");
$textareas.each(function () {
wiktkuMakeManualResizable(this);
});
}
/** URL of a template's own wiki page, via mw.util when available. */
function tplPageUrl(name) {
var title = "Şablon:" + name;
if (window.mw && mw.util && typeof mw.util.getUrl === "function") {
return mw.util.getUrl(title);
}
return "/wiki/" + encodeURIComponent(title);
}
function wrapNativeSelect(select) {
var wrapper = $("<span class='wiktku-native-select-wrap'></span>");
if (select.prop("multiple")) {
return wrapper.append(select);
}
var placeholder = select.attr("data-placeholder") || "Lê bigere...";
select.hide();
var textInput = $("<input type='search' autocomplete='off'>")
.attr("placeholder", placeholder)
.attr("role", "combobox")
.attr("aria-autocomplete", "list")
.attr("aria-expanded", "false")
.addClass("wiktku-start-narrow wiktku-ac-input");
var resultsList = $("<ul class='wiktku-ac-results' role='listbox'></ul>").hide();
var allOptions = [];
var shown = [];
var activeIndex = -1;
function cacheOptions() {
allOptions = [];
select.find("option").each(function () {
var v = $(this).val();
var text = $(this).text();
// Skip a blank value with no label; a blank value WITH a label
// (e.g. "clear the selection") is still a real, selectable option.
if (!v && !text) return;
// Language options read "Name (code)" -- score the name alone so
// the trailing code doesn't also count as a name match.
var name = text.replace(/\s*\([^()]*\)\s*$/, "");
allOptions.push({
value: v,
text: text,
disabled: $(this).prop("disabled"),
// data-tpl options render as a template row (name, page link, info icon).
tpl: $(this).attr("data-tpl") || "",
tplStatus: $(this).attr("data-tpl-status") || "",
foldCode: foldDiacritics(v),
foldName: foldDiacritics(name),
foldText: foldDiacritics(text)
});
});
}
cacheOptions();
// True if `q` starts a word inside `name` ("kevn" in "ingiliziya kevn").
function startsWord(name, q) {
var from = 0, at;
while ((at = name.indexOf(q, from)) !== -1) {
if (at === 0 || /[\s\-\u2013\u2014(/,.]/.test(name.charAt(at - 1))) return true;
from = at + 1;
}
return false;
}
// Lower rank = better match; a code match always outranks a name match.
var RANK_NONE = 99;
function rankOption(o, q) {
var code = o.foldCode, name = o.foldName;
if (code === q) return 0; // exact code
if (code.indexOf(q) === 0) return 1; // code prefix
if (name === q) return 2; // exact name
if (name.indexOf(q) === 0) return 3; // name prefix
if (startsWord(name, q)) return 4; // starts a word in the name
if (name.indexOf(q) !== -1) return 5; // name substring
if (code.indexOf(q) !== -1) return 6; // code substring
if (o.foldText.indexOf(q) !== -1) return 7; // anywhere else in the label
return RANK_NONE;
}
function positionList() {
// Position the list above or below depending on viewport space.
var inputRect = textInput[0].getBoundingClientRect();
var viewportH = window.innerHeight;
var spaceBelow = viewportH - inputRect.bottom - 8;
var spaceAbove = inputRect.top - 8;
var maxH = Math.min(224, Math.max(spaceBelow, spaceAbove)); // 224px = 14em
resultsList.css({
"max-height": maxH + "px",
"top": spaceBelow >= spaceAbove ? "100%" : "auto",
"bottom": spaceBelow >= spaceAbove ? "auto" : "100%"
});
}
function closeList() {
resultsList.hide();
textInput.attr("aria-expanded", "false");
activeIndex = -1;
}
function showResults(query) {
var q = foldDiacritics($.trim(query || ""));
resultsList.empty();
activeIndex = -1;
var matched = [];
if (q) {
$.each(allOptions, function (i, o) {
if (o.disabled) return;
var rank = rankOption(o, q);
if (rank !== RANK_NONE) matched.push({ opt: o, rank: rank, i: i });
});
if (!matched.length && q.indexOf(" ") !== -1) {
var tokens = q.split(/\s+/);
$.each(allOptions, function (i, o) {
if (o.disabled) return;
var all = true;
$.each(tokens, function (_, t) {
if (o.foldText.indexOf(t) === -1) { all = false; return false; }
});
if (all) matched.push({ opt: o, rank: 8, i: i });
});
}
matched.sort(function (a, b) {
if (a.rank !== b.rank) return a.rank - b.rank;
// Inside the code tiers the shorter code is the better hit:
// "an" -> "an" before "ang" before "ang-x-...".
if (a.rank <= 1 && a.opt.foldCode.length !== b.opt.foldCode.length) {
return a.opt.foldCode.length - b.opt.foldCode.length;
}
// Otherwise keep the menu's own order (pinned languages first).
return a.i - b.i;
});
} else {
$.each(allOptions, function (i, o) {
if (!o.disabled) matched.push({ opt: o, rank: 0, i: i });
});
}
shown = $.map(matched.slice(0, 50), function (m) { return m.opt; });
if (!shown.length) {
closeList();
return;
}
$.each(shown, function (idx, opt) {
var li = $("<li role='option'></li>")
.attr("data-value", opt.value)
.attr("aria-selected", "false");
if (opt.tpl) {
// Template row: status dot, name, then two icons that stopPropagation
// so clicking them doesn't also select the option.
li.addClass("wiktku-ac-tplrow");
li.append($("<span class='wiktku-tpl-dot'></span>")
.addClass(opt.tplStatus === "yellow" ? "wiktku-tpl-dot-special" : "wiktku-tpl-dot-ok")
.attr("title", opt.tplStatus === "yellow"
? "Tenê ji bo rewşên taybet"
: "Tê pêşniyar kirin"));
li.append($("<span class='wiktku-ac-tpltext'></span>").text(opt.text));
var icons = $("<span class='wiktku-tpl-icons'></span>");
// touchend must be stopped here too, or a tap on either icon
// selects the template row instead of activating the icon.
var STOP = "mousedown touchstart touchend touchmove";
var link = $("<a class='wiktku-tpl-icon wiktku-tpl-icon-link' target='_blank' rel='noopener'>🔗</a>")
.attr("title", "Rûpela şablonê veke")
.attr("href", tplPageUrl(opt.tpl));
// stopPropagation only -- preventDefault would stop the link from navigating.
link.on("click", function (e) { e.stopPropagation(); });
link.on(STOP, function (e) { e.stopPropagation(); });
var info = $("<button type='button' class='wiktku-tpl-icon wiktku-tpl-icon-info'>i</button>")
.attr("title", "Ev şablon çi dike?");
// mousedown blocks the focus-shift that would blur (and clear) the search box; touch events only stop propagation, since preventDefault on touchstart would suppress the button's own click handler.
info.on("mousedown", function (e) { e.preventDefault(); e.stopPropagation(); });
info.on("touchstart touchend touchmove", function (e) { e.stopPropagation(); });
info.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
// Closes the list first -- it floats over the info panel and would otherwise hide it once opened.
closeList();
var handler = select.data("wiktkuTplInfo");
if (typeof handler === "function") handler(opt.tpl, wrapper);
});
icons.append(link).append(info);
li.append(icons);
} else {
li.text(opt.text);
}
// touchend (not touchstart) so a scroll doesn't trigger a select.
var touchMoved = false;
li.on("touchstart", function () {
touchMoved = false;
});
li.on("touchmove", function () {
touchMoved = true;
});
li.on("touchend", function (e) {
if (touchMoved) return; // was a scroll, not a tap
e.preventDefault();
selectOption(opt.value, opt.text);
});
// mousedown (not click) so it beats the input's blur handler.
li.on("mousedown", function (e) {
e.preventDefault();
selectOption(opt.value, opt.text);
});
li.on("mouseenter", function () {
// Don't scroll on hover -- it would shift the list under the pointer and cascade.
setActive(idx, /* scrollIntoView */ false);
});
resultsList.append(li);
});
positionList();
resultsList.show();
resultsList[0].scrollTop = 0;
textInput.attr("aria-expanded", "true");
}
function setActive(idx, scrollIntoView) {
var items = resultsList.children("li");
if (!items.length) return;
if (idx < 0) idx = items.length - 1;
if (idx >= items.length) idx = 0;
activeIndex = idx;
items.removeClass("wiktku-ac-active").attr("aria-selected", "false");
var li = items.eq(idx).addClass("wiktku-ac-active").attr("aria-selected", "true");
if (scrollIntoView === false) return;
// Keep the active item visible for keyboard navigation.
var ul = resultsList[0], el = li[0];
if (el.offsetTop < ul.scrollTop) {
ul.scrollTop = el.offsetTop;
} else if (el.offsetTop + el.offsetHeight > ul.scrollTop + ul.clientHeight) {
ul.scrollTop = el.offsetTop + el.offsetHeight - ul.clientHeight;
}
}
/** Puts the currently selected option's label back in the text box. */
function syncInputToSelect() {
var cur = select.val();
var match = null;
if (cur) {
$.each(allOptions, function (_, o) {
if (o.value === cur) { match = o; return false; }
});
}
textInput.val(match ? match.text : "");
}
function selectOption(value, text) {
textInput.val(text);
select.val(value);
closeList();
select.trigger("change");
}
textInput.on("input", function () {
showResults($(this).val());
});
textInput.on("focus", function () {
// Select the current label so typing replaces it, and open the full list
this.select();
showResults("");
});
textInput.on("click", function () {
if (!resultsList.is(":visible")) showResults($(this).val());
});
textInput.on("keydown", function (e) {
var open = resultsList.is(":visible");
switch (e.which) {
case 40: // Down
e.preventDefault();
if (!open) { showResults($(this).val()); setActive(0); }
else setActive(activeIndex + 1);
break;
case 38: // Up
e.preventDefault();
if (!open) { showResults($(this).val()); setActive(-1); }
else setActive(activeIndex - 1);
break;
case 13: // Enter -- must never submit the form while the list is open
if (open) {
e.preventDefault();
if (activeIndex >= 0 && shown[activeIndex]) {
selectOption(shown[activeIndex].value, shown[activeIndex].text);
} else if (shown.length === 1) {
selectOption(shown[0].value, shown[0].text);
}
}
break;
case 27: // Esc -- abandon the search, restore the real value
if (open) {
e.preventDefault();
closeList();
syncInputToSelect();
}
break;
case 9: // Tab -- commit the highlighted row on the way out
if (open && activeIndex >= 0 && shown[activeIndex]) {
selectOption(shown[activeIndex].value, shown[activeIndex].text);
}
closeList();
break;
}
});
textInput.on("blur", function () {
// Short delay so a touchstart/mousedown on a result fires first.
setTimeout(function () {
closeList();
// Half-typed text that never became a selection would otherwise sit
// in the box looking like a choice -- put the real value back.
syncInputToSelect();
}, 200);
});
// Re-caches the option list. Fired after the full language list loads, after
// options are enabled/disabled, and after programmatic .val() changes.
select.on("wiktku:refresh", function () {
cacheOptions();
syncInputToSelect();
if (resultsList.is(":visible")) showResults(textInput.val());
});
wrapper.append(textInput).append(resultsList).append(select);
return wrapper;
}
/** Gives every plain <select> under `root` the searchable picker; idempotent, and .wiktku-plain-select opts out. */
function initSelects(root) {
$(root).find("select").addBack("select").not(".wiktku-plain-select").each(function () {
var sel = $(this);
if (sel.prop("multiple")) return;
if (sel.closest(".wiktku-native-select-wrap").length) return; // already wrapped
var marker = $("<span></span>").insertBefore(sel);
marker.replaceWith(wrapNativeSelect(sel));
});
}
module.exports = {
capitalize: capitalize,
decapitalize: decapitalize,
escapeRegExp: escapeRegExp,
stripWikilinks: stripWikilinks,
splitTercimeIzah: splitTercimeIzah,
insertAtCursor: insertAtCursor,
foldDiacritics: foldDiacritics,
wrapNativeSelect: wrapNativeSelect,
initSelects: initSelects,
tplPageUrl: tplPageUrl,
makeManualResizable: wiktkuMakeManualResizable,
initManualResize: wiktkuInitManualResize
};
//</nowiki>
snf7oclbnce6448xegu4thvf1n2eu7w
virker
0
1358615
6334314
2026-08-17T13:29:09Z
Wikihez
7344
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +ku:navdêr:Kesê ku derewan dike.
6334314
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=mnmane}}
# [[kes|Kesê]] ku [[derew|derewan]] [[kirin|dike]].
#: {{hevmane|ku|derewker|derewker|derewker|derewker|derewçîn|derewçîn|derewçîn|derewçîn}}
#* {{jêder|ku|jêgirtin=Berî niha wextê ko kumê sor hêj ne hati bûn vî welatî; '''virkerê''' vî welatê{{sic}} ez bûm. Ji xêrî min kesekî din nizaniya viran bike.|2={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998|nivîskar=w:Herekol Azîzan|r=835|hejmar=34|sal=1941|sernav=Mala Jêrîn}}}}
#* {{jêder-kovar|ku|jêgirtin=Mirovên ku li dij toleransê ne yên '''virker''' in. Wezîrê Hıtlerê faşıst Goebel, digortin{{sic}} ku; "dereweke baê bê tekrar kirin, dibe rast."|nivîskar=C. Şero|sernav=Tolerans-Bawerî û Ronahî|kovar=Berbang|hejmar=2|sal=1986|rûpel=17}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
8lf84u3b7mkzoektfheo9qg55k6dz7j
6334315
6334314
2026-08-17T13:29:25Z
Wikihez
7344
6334315
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=mnmane}}
# [[kes|Kesê]] ku [[derew|derewan]] [[kirin|dike]].
#: {{hevmane|ku|derewker|derewçîn}}
#* {{jêder|ku|jêgirtin=Berî niha wextê ko kumê sor hêj ne hati bûn vî welatî; '''virkerê''' vî welatê{{sic}} ez bûm. Ji xêrî min kesekî din nizaniya viran bike.|2={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998|nivîskar=w:Herekol Azîzan|r=835|hejmar=34|sal=1941|sernav=Mala Jêrîn}}}}
#* {{jêder-kovar|ku|jêgirtin=Mirovên ku li dij toleransê ne yên '''virker''' in. Wezîrê Hıtlerê faşıst Goebel, digortin{{sic}} ku; "dereweke baê bê tekrar kirin, dibe rast."|nivîskar=C. Şero|sernav=Tolerans-Bawerî û Ronahî|kovar=Berbang|hejmar=2|sal=1986|rûpel=17}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
5kfe371uc0llqfhd70o2apo33neyd9p
6334325
6334315
2026-08-17T14:06:37Z
Wikihez
7344
([[Mediawiki:Gadget-start.js|WF:NÛ]] v.2.0) +ku:rengdêr:Ya/yê derewan dike.
6334325
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Navdêr ===
{{navdêr|ku|c=mnmane}}
# [[kes|Kesê]] ku [[derew|derewan]] [[kirin|dike]].
#: {{hevmane|ku|derewker|derewçîn}}
#* {{jêder|ku|jêgirtin=Berî niha wextê ko kumê sor hêj ne hati bûn vî welatî; '''virkerê''' vî welatê{{sic}} ez bûm. Ji xêrî min kesekî din nizaniya viran bike.|2={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998|nivîskar=w:Herekol Azîzan|r=835|hejmar=34|sal=1941|sernav=Mala Jêrîn}}}}
#* {{jêder-kovar|ku|jêgirtin=Mirovên ku li dij toleransê ne yên '''virker''' in. Wezîrê Hıtlerê faşıst Goebel, digortin{{sic}} ku; "dereweke baê bê tekrar kirin, dibe rast."|nivîskar=C. Şero|sernav=Tolerans-Bawerî û Ronahî|kovar=Berbang|hejmar=2|sal=1986|rûpel=17}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
=== Rengdêr ===
{{rengdêr|ku}}
# Ya/yê [[derew|derewan]] [[dike|dike]].
#: {{hevmane|ku|derewîn}}
#* {{jêder-kitêb|ku|jêgirtin=Bê guman e derew tiştek gelek pîs û zerarak mezin jê peyda dibit û her cihê derew lê peyda bibit bi piranî ew cih wê wêran bibit; çunkî bîr û bawer li nav parçên wê komkê da namînit, ji bilî wî qasî mirovê '''virker''' derewîn biha û rûmet jê ra namînit.|nivîskar=MELE MEHMÛD DERWÊŞÎ|sernav=Miştaxa çiya ji gotinên pêşiya|sal=1989|rûpel=14}}
==== Etîmolojî ====
{{ji?|ku}}
==== Bi zaravayên din ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
mpd4uyc4wnqge5tn7u42h9sk32hojpb
6334326
6334325
2026-08-17T14:07:18Z
Wikihez
7344
6334326
wikitext
text/x-wiki
== {{ziman|ku}} ==
==== Etîmolojî ====
Ji {{qf|ku|vir|-ker}}.
=== Navdêr ===
{{navdêr|ku|c=mnmane}}
# [[kes|Kesê]] ku [[derew|derewan]] [[kirin|dike]].
#: {{hevmane|ku|derewker|derewçîn}}
#* {{jêder|ku|jêgirtin=Berî niha wextê ko kumê sor hêj ne hati bûn vî welatî; '''virkerê''' vî welatê{{sic}} ez bûm. Ji xêrî min kesekî din nizaniya viran bike.|2={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998|nivîskar=w:Herekol Azîzan|r=835|hejmar=34|sal=1941|sernav=Mala Jêrîn}}}}
#* {{jêder-kovar|ku|jêgirtin=Mirovên ku li dij toleransê ne yên '''virker''' in. Wezîrê Hıtlerê faşıst Goebel, digortin{{sic}} ku; "dereweke baê bê tekrar kirin, dibe rast."|nivîskar=C. Şero|sernav=Tolerans-Bawerî û Ronahî|kovar=Berbang|hejmar=2|sal=1986|rûpel=17}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Bi zaravayên din ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
=== Rengdêr ===
{{rengdêr|ku}}
# Ya/yê [[derew|derewan]] [[dike|dike]].
#: {{hevmane|ku|derewîn}}
#* {{jêder-kitêb|ku|jêgirtin=Bê guman e derew tiştek gelek pîs û zerarak mezin jê peyda dibit û her cihê derew lê peyda bibit bi piranî ew cih wê wêran bibit; çunkî bîr û bawer li nav parçên wê komkê da namînit, ji bilî wî qasî mirovê '''virker''' derewîn biha û rûmet jê ra namînit.|nivîskar=MELE MEHMÛD DERWÊŞÎ|sernav=Miştaxa çiya ji gotinên pêşiya|sal=1989|rûpel=14}}
==== Bi zaravayên din ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
l7mqy35z2f1grtyj5xq7fkzx7a2h0u4
6334327
6334326
2026-08-17T14:07:38Z
Wikihez
7344
/* Rengdêr */
6334327
wikitext
text/x-wiki
== {{ziman|ku}} ==
==== Etîmolojî ====
Ji {{qf|ku|vir|-ker}}.
=== Navdêr ===
{{navdêr|ku|c=mnmane}}
# [[kes|Kesê]] ku [[derew|derewan]] [[kirin|dike]].
#: {{hevmane|ku|derewker|derewçîn}}
#* {{jêder|ku|jêgirtin=Berî niha wextê ko kumê sor hêj ne hati bûn vî welatî; '''virkerê''' vî welatê{{sic}} ez bûm. Ji xêrî min kesekî din nizaniya viran bike.|2={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998|nivîskar=w:Herekol Azîzan|r=835|hejmar=34|sal=1941|sernav=Mala Jêrîn}}}}
#* {{jêder-kovar|ku|jêgirtin=Mirovên ku li dij toleransê ne yên '''virker''' in. Wezîrê Hıtlerê faşıst Goebel, digortin{{sic}} ku; "dereweke baê bê tekrar kirin, dibe rast."|nivîskar=C. Şero|sernav=Tolerans-Bawerî û Ronahî|kovar=Berbang|hejmar=2|sal=1986|rûpel=17}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Bi zaravayên din ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
=== Rengdêr ===
{{rengdêr|ku}}
# Ya/yê [[derew|derewan]] [[dike|dike]].
#: {{hevmane|ku|derewîn}}
#* {{jêder-kitêb|ku|jêgirtin=Bê guman e derew tiştek gelek pîs û zerarak mezin jê peyda dibit û her cihê derew lê peyda bibit bi piranî ew cih wê wêran bibit; çunkî bîr û bawer li nav parçên wê komkê da namînit, ji bilî wî qasî mirovê '''virker''' derewîn biha û rûmet jê ra namînit.|nivîskar=Mele Mehmûd Derwêşî|sernav=Miştaxa çiya ji gotinên pêşiya|sal=1989|rûpel=14}}
==== Bi zaravayên din ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
btv1aehyepp4jqm0i6v86dv6a6km7yc
6334328
6334327
2026-08-17T14:08:35Z
Wikihez
7344
/* Etîmolojî */ (bi [[MediaWiki:Gadget-AjaxEdit.js|AjaxEdit]]ê re)
6334328
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Etîmolojî ===
Ji {{qf|ku|vir|-ker}}.
=== Navdêr ===
{{navdêr|ku|c=mnmane}}
# [[kes|Kesê]] ku [[derew|derewan]] [[kirin|dike]].
#: {{hevmane|ku|derewker|derewçîn}}
#* {{jêder|ku|jêgirtin=Berî niha wextê ko kumê sor hêj ne hati bûn vî welatî; '''virkerê''' vî welatê{{sic}} ez bûm. Ji xêrî min kesekî din nizaniya viran bike.|2={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998|nivîskar=w:Herekol Azîzan|r=835|hejmar=34|sal=1941|sernav=Mala Jêrîn}}}}
#* {{jêder-kovar|ku|jêgirtin=Mirovên ku li dij toleransê ne yên '''virker''' in. Wezîrê Hıtlerê faşıst Goebel, digortin{{sic}} ku; "dereweke baê bê tekrar kirin, dibe rast."|nivîskar=C. Şero|sernav=Tolerans-Bawerî û Ronahî|kovar=Berbang|hejmar=2|sal=1986|rûpel=17}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Bi zaravayên din ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
=== Rengdêr ===
{{rengdêr|ku}}
# Ya/yê [[derew|derewan]] [[dike|dike]].
#: {{hevmane|ku|derewîn}}
#* {{jêder-kitêb|ku|jêgirtin=Bê guman e derew tiştek gelek pîs û zerarak mezin jê peyda dibit û her cihê derew lê peyda bibit bi piranî ew cih wê wêran bibit; çunkî bîr û bawer li nav parçên wê komkê da namînit, ji bilî wî qasî mirovê '''virker''' derewîn biha û rûmet jê ra namînit.|nivîskar=Mele Mehmûd Derwêşî|sernav=Miştaxa çiya ji gotinên pêşiya|sal=1989|rûpel=14}}
==== Bi zaravayên din ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
hdj5m7iiohd2at50v5zfskxv1gohbag
6334340
6334328
2026-08-17T15:12:07Z
Balyozbot
7867
Bot: Paqijiyên kozmetîk (Lînk paqij kir.)
6334340
wikitext
text/x-wiki
== {{ziman|ku}} ==
=== Etîmolojî ===
Ji {{qf|ku|vir|-ker}}.
=== Navdêr ===
{{navdêr|ku|c=mnmane}}
# [[kes|Kesê]] ku [[derew]]an [[kirin|dike]].
#: {{hevmane|ku|derewker|derewçîn}}
#* {{jêder|ku|jêgirtin=Berî niha wextê ko kumê sor hêj ne hati bûn vî welatî; '''virkerê''' vî welatê{{sic}} ez bûm. Ji xêrî min kesekî din nizaniya viran bike.|2={{Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998|nivîskar=w:Herekol Azîzan|r=835|hejmar=34|sal=1941|sernav=Mala Jêrîn}}}}
#* {{jêder-kovar|ku|jêgirtin=Mirovên ku li dij toleransê ne yên '''virker''' in. Wezîrê Hıtlerê faşıst Goebel, digortin{{sic}} ku; "dereweke baê bê tekrar kirin, dibe rast."|nivîskar=C. Şero|sernav=Tolerans-Bawerî û Ronahî|kovar=Berbang|hejmar=2|sal=1986|rûpel=17}}
==== Tewandin ====
{{ku-tewîn-nav|mê û nêr}}
==== Bi zaravayên din ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|kesê ku derewan dike|1}}
{{werger-bin}}
=== Rengdêr ===
{{rengdêr|ku}}
# Ya/yê [[derew]]an [[dike]].
#: {{hevmane|ku|derewîn}}
#* {{jêder-kitêb|ku|jêgirtin=Bê guman e derew tiştek gelek pîs û zerarak mezin jê peyda dibit û her cihê derew lê peyda bibit bi piranî ew cih wê wêran bibit; çunkî bîr û bawer li nav parçên wê komkê da namînit, ji bilî wî qasî mirovê '''virker''' derewîn biha û rûmet jê ra namînit.|nivîskar=Mele Mehmûd Derwêşî|sernav=Miştaxa çiya ji gotinên pêşiya|sal=1989|rûpel=14}}
==== Bi zaravayên din ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
==== Werger ====
{{werger-ser|ya/yê derewan dike|1}}
{{werger-bin}}
0zmrm5ogif2ibzkt4ei3hf5cpry1kml
Gotûbêj:monoteîst
1
1358616
6334324
2026-08-17T13:57:09Z
Zerwanda
12183
/* Deynkirina tirkî */ beşeke nû
6334324
wikitext
text/x-wiki
== Deynkirina tirkî ==
[[User:Ghybu]] Qiseya ne-Kurdî ya "monoteîst" ji Tirkî ye çunkî 1. Eger Tirkî ev gotina Ewropî deyn nekira (ji bona miraza wan ya ku bibine mîna wan) Kurdan jî bi xweser ev deyn nekira. 2. Kurdan jixwe *bi rastî* deyn nekiriye, li nava qisedana gel yek merdivek peyda nabe ku vê gotinê bi xwecihî u asayî bi kar îne bi heman rehetiya gotineke din ya Kurdî, mîna ku meriv bêje "dest, pê, şîr, dayik" u nizanim çi. 3. Zar u zimanên li dor Kurdan vê gotinê bi kar naînin, bi Farisî dibêjin "yektaperest" u Erebî jixwe dibêjin "muwehhid". Çawa ku meriv nikare di zarê Kurdî da bibêje "tektanrîçî" meriv herwisa nikare bêje "monoteîst". Zena ku Kurd dikarin hima bêyî bandora Romiyan vê qiseyê deyn bikin nabe hêjayî çu rijdiyê jî. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 13:57, 17 tebax 2026 (UTC)
kapyl9gtvr1tfu9osphp18rd6au8mav
6334332
6334324
2026-08-17T14:48:25Z
Ghybu
1393
/* Deynkirina tirkî */ Bersiv
6334332
wikitext
text/x-wiki
== Deynkirina tirkî ==
[[User:Ghybu]] Qiseya ne-Kurdî ya "monoteîst" ji Tirkî ye çunkî 1. Eger Tirkî ev gotina Ewropî deyn nekira (ji bona miraza wan ya ku bibine mîna wan) Kurdan jî bi xweser ev deyn nekira. 2. Kurdan jixwe *bi rastî* deyn nekiriye, li nava qisedana gel yek merdivek peyda nabe ku vê gotinê bi xwecihî u asayî bi kar îne bi heman rehetiya gotineke din ya Kurdî, mîna ku meriv bêje "dest, pê, şîr, dayik" u nizanim çi. 3. Zar u zimanên li dor Kurdan vê gotinê bi kar naînin, bi Farisî dibêjin "yektaperest" u Erebî jixwe dibêjin "muwehhid". Çawa ku meriv nikare di zarê Kurdî da bibêje "tektanrîçî" meriv herwisa nikare bêje "monoteîst". Zena ku Kurd dikarin hima bêyî bandora Romiyan vê qiseyê deyn bikin nabe hêjayî çu rijdiyê jî. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 13:57, 17 tebax 2026 (UTC)
:Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine nivŝandin wekî deyn ji tirkî bikinê:) Ev peyv di cîhana akademîk de tê bikaranîn, kesên ku neçûne mektebê nizanin: jin, mêr û zarokên tirk jî nizanin. Herwiha peyva monoteîst ji farsî de jî tê bikaranîn {{q|fa|مونوتیسم}} ([https://fa.wikipedia.org/wiki/%DB%8C%DA%A9%D8%AA%D8%A7%D9%BE%D8%B1%D8%B3%D8%AA%DB%8C]) (bnr. Monotheism ji fa.wikipedia).
:Li Ewropayê pirr nivîskar, akademîsyen û enstîtûyên kurd hene (Parîs, stockholm, Berlin, ...). Pirr rojname, kovar (Kurmancî, ''le monde diplomatique'' bi kurdî, ...) û pirtûk dirxistine, vana zimanên înglîzî, almanî û fransî deng dikin. Dema ku kurdî qedexe bû, tenê li Ewropayê dihat nivîsandin. Kurdên Ewropayê peyv ji zimanên Ewropayê digirin ne li zimanê tirkî. Pirtûka rêzimanê ya herî navdar ji hêla Berxan û Lascot (fransiz) ve hatiye nivîsandin, Bedirxan li Sûriyê bi fransizan re dixebitiya. Mektûba Nûrî Dêrsimî bi zimanê fransî hatiye nivîsandin :)) Li gorî te di kurdan de aktemîsyên tine ye. Belkî te li metebê li Tirkiyê hû kiriye feqet ji bo herkes ne wisa ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 17 tebax 2026 (UTC)
lfe7zwcbxkxm583brvg9fil7tmf71eo
6334334
6334332
2026-08-17T14:54:03Z
Ghybu
1393
/* Deynkirina tirkî */
6334334
wikitext
text/x-wiki
== Deynkirina tirkî ==
[[User:Ghybu]] Qiseya ne-Kurdî ya "monoteîst" ji Tirkî ye çunkî 1. Eger Tirkî ev gotina Ewropî deyn nekira (ji bona miraza wan ya ku bibine mîna wan) Kurdan jî bi xweser ev deyn nekira. 2. Kurdan jixwe *bi rastî* deyn nekiriye, li nava qisedana gel yek merdivek peyda nabe ku vê gotinê bi xwecihî u asayî bi kar îne bi heman rehetiya gotineke din ya Kurdî, mîna ku meriv bêje "dest, pê, şîr, dayik" u nizanim çi. 3. Zar u zimanên li dor Kurdan vê gotinê bi kar naînin, bi Farisî dibêjin "yektaperest" u Erebî jixwe dibêjin "muwehhid". Çawa ku meriv nikare di zarê Kurdî da bibêje "tektanrîçî" meriv herwisa nikare bêje "monoteîst". Zena ku Kurd dikarin hima bêyî bandora Romiyan vê qiseyê deyn bikin nabe hêjayî çu rijdiyê jî. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 13:57, 17 tebax 2026 (UTC)
:Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine girtin wekî deyn ji tirkî bikinê:) Ev peyv di cîhana akademîk de tê bikaranîn, kesên ku neçûne mektebê nizanin: jin, mêr û zarokên tirk jî nizanin. Herwiha peyva monoteîst ji farsî de jî tê bikaranîn {{q|fa|مونوتیسم}} ([https://fa.wikipedia.org/wiki/%DB%8C%DA%A9%D8%AA%D8%A7%D9%BE%D8%B1%D8%B3%D8%AA%DB%8C]) (bnr. Monotheism ji fa.wikipedia).
:Li Ewropayê pirr nivîskar, akademîsyen û enstîtûyên kurd hene (Parîs, stockholm, Berlin, ...). Pirr rojname, kovar (Kurmancî, ''le monde diplomatique'' bi kurdî, ...) û pirtûk dirxistine, vana zimanên înglîzî, almanî û fransî deng dikin. Dema ku kurdî qedexe bû, tenê li Ewropayê dihat nivîsandin. Kurdên Ewropayê peyv ji zimanên Ewropayê digirin ne li zimanê tirkî. Pirtûka rêzimanê ya herî navdar ji hêla Berxan û Lascot (fransiz) ve hatiye nivîsandin, Bedirxan li Sûriyê bi fransizan re dixebitiya. Mektûba Nûrî Dêrsimî bi zimanê fransî hatiye nivîsandin :)) Li gorî te di kurdan de aktemîsyên tine ye. Belkî te li mektebên Tirkiyeyê hû kiriye feqet ji bo herkes ne wisa ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 17 tebax 2026 (UTC)
cva5t9b25zxsxqmij1k9a4pj5a2jbaw
6334335
6334334
2026-08-17T14:56:46Z
Ghybu
1393
/* Deynkirina tirkî */
6334335
wikitext
text/x-wiki
== Deynkirina tirkî ==
[[User:Ghybu]] Qiseya ne-Kurdî ya "monoteîst" ji Tirkî ye çunkî 1. Eger Tirkî ev gotina Ewropî deyn nekira (ji bona miraza wan ya ku bibine mîna wan) Kurdan jî bi xweser ev deyn nekira. 2. Kurdan jixwe *bi rastî* deyn nekiriye, li nava qisedana gel yek merdivek peyda nabe ku vê gotinê bi xwecihî u asayî bi kar îne bi heman rehetiya gotineke din ya Kurdî, mîna ku meriv bêje "dest, pê, şîr, dayik" u nizanim çi. 3. Zar u zimanên li dor Kurdan vê gotinê bi kar naînin, bi Farisî dibêjin "yektaperest" u Erebî jixwe dibêjin "muwehhid". Çawa ku meriv nikare di zarê Kurdî da bibêje "tektanrîçî" meriv herwisa nikare bêje "monoteîst". Zena ku Kurd dikarin hima bêyî bandora Romiyan vê qiseyê deyn bikin nabe hêjayî çu rijdiyê jî. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 13:57, 17 tebax 2026 (UTC)
:Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine girtin wekî deyn ji tirkî bikinê:) Ev peyv di cîhana akademîk de tê bikaranîn, kesên ku neçûne mektebê nizanin: jin, mêr û zarokên tirk jî nizanin. Herwiha peyva monoteîst ji farsî de jî tê bikaranîn {{q|fa|مونوتیسم}} ([https://fa.wikipedia.org/wiki/%DB%8C%DA%A9%D8%AA%D8%A7%D9%BE%D8%B1%D8%B3%D8%AA%DB%8C]) (bnr. Monotheism ji fa.wikipedia).
:Li Ewropayê pirr nivîskar, akademîsyen û enstîtûyên kurd hene (Parîs, stockholm, Berlin, ...). Pirr rojname, kovar (Kurmancî, ''le monde diplomatique'' bi kurdî, ...) û pirtûk dirxistine, vana zimanên înglîzî, almanî û fransî deng dikin. Dema ku kurdî qedexe bû, tenê li Ewropayê dihat nivîsandin. Kurdên Ewropayê peyv ji zimanên Ewropayê digirin ne li zimanê tirkî. Pirtûka rêzimanê ya herî navdar ji hêla Berxan û Lascot (fransiz) ve hatiye nivîsandin, Bedirxan li Sûriyê bi fransizan re dixebitiya. Mektûba Nûrî Dêrsimî bi zimanê fransî hatiye nivîsandin :)) Li gorî te di kurdan de aktemîsyên tine ye? Belkî te li mektebên Tirkiyeyê hû kiriye feqet ji bo herkes ne wisa ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 17 tebax 2026 (UTC)
m5v8ff7gxyvyppqhuxjcb4bx19bwn6p
6334338
6334335
2026-08-17T15:03:01Z
Zerwanda
12183
/* Deynkirina tirkî */ Bersiv
6334338
wikitext
text/x-wiki
== Deynkirina tirkî ==
[[User:Ghybu]] Qiseya ne-Kurdî ya "monoteîst" ji Tirkî ye çunkî 1. Eger Tirkî ev gotina Ewropî deyn nekira (ji bona miraza wan ya ku bibine mîna wan) Kurdan jî bi xweser ev deyn nekira. 2. Kurdan jixwe *bi rastî* deyn nekiriye, li nava qisedana gel yek merdivek peyda nabe ku vê gotinê bi xwecihî u asayî bi kar îne bi heman rehetiya gotineke din ya Kurdî, mîna ku meriv bêje "dest, pê, şîr, dayik" u nizanim çi. 3. Zar u zimanên li dor Kurdan vê gotinê bi kar naînin, bi Farisî dibêjin "yektaperest" u Erebî jixwe dibêjin "muwehhid". Çawa ku meriv nikare di zarê Kurdî da bibêje "tektanrîçî" meriv herwisa nikare bêje "monoteîst". Zena ku Kurd dikarin hima bêyî bandora Romiyan vê qiseyê deyn bikin nabe hêjayî çu rijdiyê jî. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 13:57, 17 tebax 2026 (UTC)
:Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine girtin wekî deyn ji tirkî bikinê:) Ev peyv di cîhana akademîk de tê bikaranîn, kesên ku neçûne mektebê nizanin: jin, mêr û zarokên tirk jî nizanin. Herwiha peyva monoteîst ji farsî de jî tê bikaranîn {{q|fa|مونوتیسم}} ([https://fa.wikipedia.org/wiki/%DB%8C%DA%A9%D8%AA%D8%A7%D9%BE%D8%B1%D8%B3%D8%AA%DB%8C]) (bnr. Monotheism ji fa.wikipedia).
:Li Ewropayê pirr nivîskar, akademîsyen û enstîtûyên kurd hene (Parîs, stockholm, Berlin, ...). Pirr rojname, kovar (Kurmancî, ''le monde diplomatique'' bi kurdî, ...) û pirtûk dirxistine, vana zimanên înglîzî, almanî û fransî deng dikin. Dema ku kurdî qedexe bû, tenê li Ewropayê dihat nivîsandin. Kurdên Ewropayê peyv ji zimanên Ewropayê digirin ne li zimanê tirkî. Pirtûka rêzimanê ya herî navdar ji hêla Berxan û Lascot (fransiz) ve hatiye nivîsandin, Bedirxan li Sûriyê bi fransizan re dixebitiya. Mektûba Nûrî Dêrsimî bi zimanê fransî hatiye nivîsandin :)) Li gorî te di kurdan de aktemîsyên tine ye? Belkî te li mektebên Tirkiyeyê hû kiriye feqet ji bo herkes ne wisa ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 17 tebax 2026 (UTC)
::>Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine nivŝandin wekî deyn ji tirkî bikinê:)
::Erê weleh, jixwe ekseriya wan jî wisan e. Bi ya min bûya çu qiseya giştî ya ku pêşiya 1900an di zar u zaravên bavkalên me da nebû yekser jê bira, lê hûn dixwazin bikevine heman xetaya ku Romî ketin. Ewa jî bi we maye.
::Cîhana "akademîk" (ha dîsa gotineke xerîb) gelo nikare wan qiseyan bi kar îne ku Ehmedê Xanî yan Mela Mehmûdê Bazîdî bi kar tanî? Gelo em mecbûr ji zar u zimanên xelkê bidizin, gelo hûn jî dibêjin em besî xwe nînin çawan ku Romî derheq xwe da dibêjin? Eceman gotina مونوتیسم (u heta bi herfên sererrast jî nenivîsîne, wekî din divê bibûya مونو'''ث'''یسم bi -th-) jî bi heman teqlîda Rojavayiyan deyn kiriye, lê eger tu kar u xebatên wan yên di zimanê xwe da bibînî yan qiseya Farisî yan jî ya Erebî dibêjin, "mûnûtîst" yan nizam çi tenê heye ku hin Faris xwe "akademîk"tir bikin u li zar u miletê xwe yê bixwe bi çehvên biçûktiyê binihêrrin, eşkere yan jî bêyî hemdê xwe. Gelo divê em jî heman çiştî bikin?
::>Li gorî te di kurdan de aktemîsyên tine ye.
::Tu nuqteya min qet fehm jî nakî. Hêj wan ":))"ana bavêj u çi dixwazî bik. Min dehwa xwe kir. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 15:03, 17 tebax 2026 (UTC)
gtfa3x7elsiwxi8i1j2xw45rm9rfgme
6334339
6334338
2026-08-17T15:03:30Z
Ghybu
1393
/* Deynkirina tirkî */
6334339
wikitext
text/x-wiki
== Deynkirina tirkî ==
[[User:Ghybu]] Qiseya ne-Kurdî ya "monoteîst" ji Tirkî ye çunkî 1. Eger Tirkî ev gotina Ewropî deyn nekira (ji bona miraza wan ya ku bibine mîna wan) Kurdan jî bi xweser ev deyn nekira. 2. Kurdan jixwe *bi rastî* deyn nekiriye, li nava qisedana gel yek merdivek peyda nabe ku vê gotinê bi xwecihî u asayî bi kar îne bi heman rehetiya gotineke din ya Kurdî, mîna ku meriv bêje "dest, pê, şîr, dayik" u nizanim çi. 3. Zar u zimanên li dor Kurdan vê gotinê bi kar naînin, bi Farisî dibêjin "yektaperest" u Erebî jixwe dibêjin "muwehhid". Çawa ku meriv nikare di zarê Kurdî da bibêje "tektanrîçî" meriv herwisa nikare bêje "monoteîst". Zena ku Kurd dikarin hima bêyî bandora Romiyan vê qiseyê deyn bikin nabe hêjayî çu rijdiyê jî. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 13:57, 17 tebax 2026 (UTC)
:Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine girtin wekî deyn ji tirkî bikinê:) Ev peyv di cîhana akademîk de tê bikaranîn, kesên ku neçûne mektebê nizanin: jin, mêr û zarokên tirk jî nizanin. Herwiha peyva monoteîst ji farsî de jî tê bikaranîn {{q|fa|مونوتیسم}} ([https://fa.wikipedia.org/wiki/%DB%8C%DA%A9%D8%AA%D8%A7%D9%BE%D8%B1%D8%B3%D8%AA%DB%8C]) (bnr. Monotheism ji fa.wikipedia). Bnr. Wikidata [https://www.wikidata.org/wiki/Q9159#sitelinks-wikipedia] di nav pirr zimanan de tê bikaranîn (cîhana akademîk de tê bikaranîn).
:Li Ewropayê pirr nivîskar, akademîsyen û enstîtûyên kurd hene (Parîs, stockholm, Berlin, ...). Pirr rojname, kovar (Kurmancî, ''le monde diplomatique'' bi kurdî, ...) û pirtûk dirxistine, vana zimanên înglîzî, almanî û fransî deng dikin. Dema ku kurdî qedexe bû, tenê li Ewropayê dihat nivîsandin. Kurdên Ewropayê peyv ji zimanên Ewropayê digirin ne li zimanê tirkî. Pirtûka rêzimanê ya herî navdar ji hêla Berxan û Lascot (fransiz) ve hatiye nivîsandin, Bedirxan li Sûriyê bi fransizan re dixebitiya. Mektûba Nûrî Dêrsimî bi zimanê fransî hatiye nivîsandin :)) Li gorî te di kurdan de aktemîsyên tine ye? Belkî te li mektebên Tirkiyeyê hû kiriye feqet ji bo herkes ne wisa ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 17 tebax 2026 (UTC)
::>Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine nivŝandin wekî deyn ji tirkî bikinê:)
::Erê weleh, jixwe ekseriya wan jî wisan e. Bi ya min bûya çu qiseya giştî ya ku pêşiya 1900an di zar u zaravên bavkalên me da nebû yekser jê bira, lê hûn dixwazin bikevine heman xetaya ku Romî ketin. Ewa jî bi we maye.
::Cîhana "akademîk" (ha dîsa gotineke xerîb) gelo nikare wan qiseyan bi kar îne ku Ehmedê Xanî yan Mela Mehmûdê Bazîdî bi kar tanî? Gelo em mecbûr ji zar u zimanên xelkê bidizin, gelo hûn jî dibêjin em besî xwe nînin çawan ku Romî derheq xwe da dibêjin? Eceman gotina مونوتیسم (u heta bi herfên sererrast jî nenivîsîne, wekî din divê bibûya مونو'''ث'''یسم bi -th-) jî bi heman teqlîda Rojavayiyan deyn kiriye, lê eger tu kar u xebatên wan yên di zimanê xwe da bibînî yan qiseya Farisî yan jî ya Erebî dibêjin, "mûnûtîst" yan nizam çi tenê heye ku hin Faris xwe "akademîk"tir bikin u li zar u miletê xwe yê bixwe bi çehvên biçûktiyê binihêrrin, eşkere yan jî bêyî hemdê xwe. Gelo divê em jî heman çiştî bikin?
::>Li gorî te di kurdan de aktemîsyên tine ye.
::Tu nuqteya min qet fehm jî nakî. Hêj wan ":))"ana bavêj u çi dixwazî bik. Min dehwa xwe kir. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 15:03, 17 tebax 2026 (UTC)
6co5z7rnajenpcd1lh2q3a2vhkajwbm
6334344
6334339
2026-08-17T15:19:15Z
Ghybu
1393
/* Deynkirina tirkî */ Bersiv
6334344
wikitext
text/x-wiki
== Deynkirina tirkî ==
[[User:Ghybu]] Qiseya ne-Kurdî ya "monoteîst" ji Tirkî ye çunkî 1. Eger Tirkî ev gotina Ewropî deyn nekira (ji bona miraza wan ya ku bibine mîna wan) Kurdan jî bi xweser ev deyn nekira. 2. Kurdan jixwe *bi rastî* deyn nekiriye, li nava qisedana gel yek merdivek peyda nabe ku vê gotinê bi xwecihî u asayî bi kar îne bi heman rehetiya gotineke din ya Kurdî, mîna ku meriv bêje "dest, pê, şîr, dayik" u nizanim çi. 3. Zar u zimanên li dor Kurdan vê gotinê bi kar naînin, bi Farisî dibêjin "yektaperest" u Erebî jixwe dibêjin "muwehhid". Çawa ku meriv nikare di zarê Kurdî da bibêje "tektanrîçî" meriv herwisa nikare bêje "monoteîst". Zena ku Kurd dikarin hima bêyî bandora Romiyan vê qiseyê deyn bikin nabe hêjayî çu rijdiyê jî. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 13:57, 17 tebax 2026 (UTC)
:Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine girtin wekî deyn ji tirkî bikinê:) Ev peyv di cîhana akademîk de tê bikaranîn, kesên ku neçûne mektebê nizanin: jin, mêr û zarokên tirk jî nizanin. Herwiha peyva monoteîst ji farsî de jî tê bikaranîn {{q|fa|مونوتیسم}} ([https://fa.wikipedia.org/wiki/%DB%8C%DA%A9%D8%AA%D8%A7%D9%BE%D8%B1%D8%B3%D8%AA%DB%8C]) (bnr. Monotheism ji fa.wikipedia). Bnr. Wikidata [https://www.wikidata.org/wiki/Q9159#sitelinks-wikipedia] di nav pirr zimanan de tê bikaranîn (cîhana akademîk de tê bikaranîn).
:Li Ewropayê pirr nivîskar, akademîsyen û enstîtûyên kurd hene (Parîs, stockholm, Berlin, ...). Pirr rojname, kovar (Kurmancî, ''le monde diplomatique'' bi kurdî, ...) û pirtûk dirxistine, vana zimanên înglîzî, almanî û fransî deng dikin. Dema ku kurdî qedexe bû, tenê li Ewropayê dihat nivîsandin. Kurdên Ewropayê peyv ji zimanên Ewropayê digirin ne li zimanê tirkî. Pirtûka rêzimanê ya herî navdar ji hêla Berxan û Lascot (fransiz) ve hatiye nivîsandin, Bedirxan li Sûriyê bi fransizan re dixebitiya. Mektûba Nûrî Dêrsimî bi zimanê fransî hatiye nivîsandin :)) Li gorî te di kurdan de aktemîsyên tine ye? Belkî te li mektebên Tirkiyeyê hû kiriye feqet ji bo herkes ne wisa ye. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 14:48, 17 tebax 2026 (UTC)
::>Bi vî awayî em ê hemû peyvên ku ji înglîzî û fransî hatine nivŝandin wekî deyn ji tirkî bikinê:)
::Erê weleh, jixwe ekseriya wan jî wisan e. Bi ya min bûya çu qiseya giştî ya ku pêşiya 1900an di zar u zaravên bavkalên me da nebû yekser jê bira, lê hûn dixwazin bikevine heman xetaya ku Romî ketin. Ewa jî bi we maye.
::Cîhana "akademîk" (ha dîsa gotineke xerîb) gelo nikare wan qiseyan bi kar îne ku Ehmedê Xanî yan Mela Mehmûdê Bazîdî bi kar tanî? Gelo em mecbûr ji zar u zimanên xelkê bidizin, gelo hûn jî dibêjin em besî xwe nînin çawan ku Romî derheq xwe da dibêjin? Eceman gotina مونوتیسم (u heta bi herfên sererrast jî nenivîsîne, wekî din divê bibûya مونو'''ث'''یسم bi -th-) jî bi heman teqlîda Rojavayiyan deyn kiriye, lê eger tu kar u xebatên wan yên di zimanê xwe da bibînî yan qiseya Farisî yan jî ya Erebî dibêjin, "mûnûtîst" yan nizam çi tenê heye ku hin Faris xwe "akademîk"tir bikin u li zar u miletê xwe yê bixwe bi çehvên biçûktiyê binihêrrin, eşkere yan jî bêyî hemdê xwe. Gelo divê em jî heman çiştî bikin?
::>Li gorî te di kurdan de aktemîsyên tine ye.
::Tu nuqteya min qet fehm jî nakî. Hêj wan ":))"ana bavêj u çi dixwazî bik. Min dehwa xwe kir. [[Bikarhêner:Zerwanda|Zerwanda]] ([[Gotûbêja bikarhêner:Zerwanda|gotûbêj]]) 15:03, 17 tebax 2026 (UTC)
:::Erê tu jî rast diwê :) Ez difikirim ku divê em tirkî jî lê zêde bikin. Feqet bi min bikaranîna "dizîn" ji bo peyvan ne baş e. [[Bikarhêner:Ghybu|Ghybu]] ([[Gotûbêja bikarhêner:Ghybu|gotûbêj]]) 15:19, 17 tebax 2026 (UTC)
s5dok9ew8yti87yx94fk29b2zu1jczr
Şablon:R:NHK Hatsuon
10
1358617
6334384
2026-08-17T19:15:30Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:NHK Hatsuon]] weke [[Şablon:Ç:ja:NHK Hatsuon]] guhart
6334384
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:Ç:ja:NHK Hatsuon]]
29s7eg1hwir56xduldles0vqg8m7grc
Şablon:R:Daijirin
10
1358618
6334387
2026-08-17T19:19:59Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:R:Daijirin]] weke [[Şablon:Ç:ja:Daijirin]] guhart
6334387
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:Ç:ja:Daijirin]]
rzp40q1bgeb4eis323r6z4m358iaur9
Şablon:R:大辞林
10
1358619
6334388
2026-08-17T19:20:10Z
Wikihez
7344
Ji bo [[Şablon:Ç:ja:Daijirin]] hat beralîkirin
6334388
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:Ç:ja:Daijirin]]
rzp40q1bgeb4eis323r6z4m358iaur9
seibutsugaku
0
1358620
6334391
2026-08-17T19:22:34Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: Ji [[en:seibutsugaku|en.wikt]]
6334391
wikitext
text/x-wiki
== {{ziman|ja}} ==
===Romanîzekirin===
{{ja-romaji}}
# {{ja-romanization of|せいぶつがく}}
jqbqr678f1j917ykqj04uivk7cidvkp
Şablon:ja-see
10
1358621
6334392
2026-08-17T19:22:56Z
Wikihez
7344
[[:en:Template:ja-see]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6334392
wikitext
text/x-wiki
<includeonly><onlyinclude>{{safesubst:<noinclude/>#invoke:ja-see|show}}</onlyinclude></includeonly><!--
-->{{documentation}}
ab8unwgqhq0eip8iztwcoswqn3dxkgp
Şablon:ja-see/belge
10
1358622
6334393
2026-08-17T19:22:57Z
Wikihez
7344
[[:en:Template:ja-see/documentation]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6334393
wikitext
text/x-wiki
{{documentation subpage}}
{{uses lua|Module:ja-see}}
Soft-redirect template for Japanese entries.
This template can be used to redirect alternative spellings to lemma entries. The advantage over {{şx|alternative spelling of|ja}} is that: (1) This template can be the whole content under <code>==Japanese==</code> or <code>===Etymology <var>n</var>===</code>, so that you don't need to copy the PoS header and the headword template from the lemma entry to the alternative spelling entries. (2) The template offers a preview of the lemma entry, currently the definitions and the other alternative spellings (more can be added in the future).
See {{qal|ja|い}} and {{qal|ja|貴方}} for examples.
==Usage==
; {{para|1}} and {{para|2}}, {{para|3}} ...
: Entries to redirect to.
; {{para|type|opt=1}} and {{para|type1}}, {{para|type2}}, {{para|type3}} ...
: Type of the redirect, which explains the relation between the redirect entry and the main entry. Predefined types are:
:* {{para|type|rom}}: Romaji.
:* {{para|type|hira}}: Hiragana spelling.
:* {{para|type|kata}}: Katakana spelling.
:* {{para|type|hkana}}: Historical kana spelling.
:* {{para|type|kyu}}: Kyūjitai.
:* {{para|type|kyualt}}: Kyūjitai of an alternative form.
:* {{para|type|alt}}: Alternative form.
: The template can usually detect the redirect types above by itself so this parameter is not needed for them.
:* {{para|type|eshin}}: Extended shinjitai.
: Custom redirect types are also accepted:
:* {{para|type|an uncommon shinjitai form}}
; {{para|key|opt=1}} and {{para|key2}}, {{para|key3}} ...
: This parameter is a kana form used to further filter the alternative forms by reading. It is only used when {{para|type1|alt}} or {{para|type1|kyualt}}. It has no effect otherwise.
; {{para|term}}
: This parameter is used on kana entries to force a transliteration when the automatically generated one is undesired. E.g. <code>term=きに.いる</code> on the page [[きにいる]] to force the transliteration ''kiniiru'' instead of ''kinīru''.
==Requirements on the lemma entry==
This template displays the relevant definitions on the entries it redirects to. Definitions are considered relevant if:
* In the case of {{para|type|rom}}, {{para|type|hira}}, {{para|type|kata}}, one of [[:Category:Japanese headword-line templates|the headword templates]] is required, such as {{temp|ja-noun}}, {{temp|ja-verb}}, etc. The title of the redirect entry should be consistent with a kana spelling in parameters passed to the headword template.
* In the case of {{para|type|alt}}, {{para|type|kyualt}}, the title of the redirect entry (or its shinjitai form) should be listed in either {{temp|ja-kanjitab|alt=...}} or {{temp|ja-def|...}}. If {{para|key}} exists, it should also be one of the kana spellings in the headword templates.
* In the case of {{para|type|kyu}}, the title of the redirect entry should be the kyūjitai of the main entry.
* In the case of all other redirect types, including custom ones, there is no requirement. All definitions found are considered relevant.
==Placement of the template==
==={{l|ja|かえる}}===
<pre>
==Japanese==
===Etymology x===
{{ja-see|蛙}}
</pre>
==={{l|ja|蝦}}===
<pre>
==Japanese==
===Etymology x===
{{ja-kanjitab|かえる|yomi=k}}
{{ja-see|蛙}}
</pre>
==={{l|ja|言葉}}===
<pre>
==Japanese==
{{ja-kanjitab|こと|は|k2=ば|yomi=k}}
{{ja-see|ことば}}
</pre>
==={{l|ja|真っ当}}===
<pre>
==Japanese==
{{ja-kanjitab|ま|とう|yomi=k,o|ateji=y}}
===Etymology===
This spelling is an example of {{ja-ateji}}, based on a reanalysis of the term as {{prefix|ja|真っ|tr1=maQ-|pos1=intensifying prefix|当|tr2=tō|t2=[[right]], [[proper]]}}.
===Definitions===
{{ja-see|まっとう}}
</pre>
==={{l|ja|貴方}}===
<pre>
==Japanese==
===Etymology 1===
{{ja-kanjitab|yomi=jukujikun}}
{{ja-see|あなた|jukujikun}}
===Etymology 2===
...
</pre>
or
<pre>
==Japanese==
===Etymology 1===
{{ja-kanjitab|yomi=irreg}}
This spelling is an example of {{ja-jukujikun}}, first attested in ….
====Definitions====
{{ja-see|あなた|jukujikun}}
===Etymology 2===
...
</pre>
==={{l|ja|迄}}===
<pre>
==Japanese==
===Kanji===
...
{{ja-kanjitab|まで|yomi=k}}
===Definitions===
{{ja-see|まで}}
</pre>
or
<pre>
==Japanese==
===Kanji===
...
{{ja-kanjitab|まで|yomi=k}}
===Etymology===
This spelling is ….
===Definitions===
{{ja-see|まで}}
</pre>
(It is also okay to put the kanjitab immediately below the header.)
==Note==
Please use this template for alternative spellings in the Japanese script only. Alternative sound forms (e.g. {{qal|ja|おっぱらう}}) and alternative spellings in other scripts (e.g. [[H#Japanese]]) currently still use the old approach.
==See also==
* {{temp|ja-gv}}, for ''kyujitai'' (old kanji) forms
* {{temp|ja-see-kango}}, for grouping Sino-Japanese terms into one etymology section to save space.
<includeonly>
[[Category:Japanese templates|see]]
[[Category:Disambiguation templates]]
</includeonly>
tei03phily0od55lhu4d1a1ofv8wwck
せいぶつがく
0
1358623
6334394
2026-08-17T19:23:18Z
Wikihez
7344
[[Bikarhêner:Wikihez/templateScript2.js|TS]]: Ji [[en:せいぶつがく|en.wikt]]
6334394
wikitext
text/x-wiki
== {{ziman|ja}} ==
{{ja-see|生物学}}
5kpsi4bank24ekyda564nxeqv1fdkjw
Modul:ja-see
828
1358624
6334395
2026-08-17T19:23:39Z
Wikihez
7344
[[:en:Module:ja-see]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6334395
Scribunto
text/plain
-- Prevent substitution.
if mw.isSubsting() then
return require("Module:unsubst")
end
local m_ja = require("Module:ja")
local m_sc = require("Module:scripts")
local m_str_utils = require("Module:string utilities")
local concat = table.concat
local format_cat = require("Module:utilities").format_categories
local insert = table.insert
local load_data = mw.loadData
local maintenance_cats = require("Module:headword").maintenance_cats
local new_title = mw.title.new
local split = m_str_utils.split
local ugmatch = mw.ustring.gmatch
local ugsub = mw.ustring.gsub
local ulen = m_str_utils.len
local umatch = m_str_utils.match
local usub = m_str_utils.sub
local export = {}
local kana_to_romaji = require("Module:Hrkt-translit").tr
local headword_data = load_data("Module:headword/data")
local pagename = headword_data.pagename
local lang = require('Module:languages').getByCode("ja")
local Hira = m_sc.getByCode("Hira")
local Kana = m_sc.getByCode("Kana")
local Hrkt = m_sc.getByCode("Hrkt")
local langname = lang:getCanonicalName()
local data = load_data("Module:ja/data")
local d_kyu = load_data("Module:ja/data/kyu")
local range = load_data("Module:ja/data/range")
local function gmatch_array(s, pattern)
local output = {}
for e in ugmatch(s, pattern) do
insert(output, e)
end
return output
end
local function map(arr, f)
local output = {}
for _, e in ipairs(arr) do
local fe = f(e)
if fe ~= nil then
insert(output, fe)
end
end
return output
end
local function filter(arr, f)
local output = {}
for _, e in ipairs(arr) do
if f(e) then
insert(output, e)
end
end
return output
end
local function contains(arr, item)
for _, e in ipairs(arr) do
if e == item then
return true
end
end
return false
end
-- f should be str->str in the following functions
local function memoize(f)
local output = {}
return function(s)
if not output[s] then
output[s] = f(s)
end
return output[s]
end
end
local getContent_memo = memoize(function(title)
return new_title(title):getContent() or ''
end)
local function group(arr, f)
local r = {}
for _, e in ipairs(arr) do
local fe = f(e)
if r[#r] and r[#r].key == fe then
insert(r[#r], e)
else
insert(r, { e, key = fe })
end
end
return r
end
local function ja(text)
return '<span lang="ja" class="Jpan">' .. text .. '</span>'
end
local function link(lemma, display)
return ja("[[" .. lemma .. "#" .. langname .. "|" .. (display or lemma) .. "]]")
end
local function link_bracket(lemma, display)
return ja("【[[" .. lemma .. "#" .. langname .. "|" .. (display or lemma) .. "]]】")
end
local result
--[[ returns an array of definitions, each having the format
{
def = <definition>,
kanji_spellings = <array of alternative kanji spellings listed in {{ja-kanjitab|alt=...}}, can be overrided with {{ja-def|...}}>,
kana_spellings = <array of alternative kana spellings listed in the headword template>,
historical_kana_spellings = <array of historical kana spellings listed in the headword template>,
header = <name of PoS header>,
headword_line = <wikicode of headword line>,
}
]]
local function get_definitions_from_wikicode(wikicode, title)
local current_kanji_spellings = {}
local current_kanji_spellings_with_labels = {}
local current_kana_spellings = {}
local current_historical_kana_spellings = {}
local current_head_level = '=='
local current_header = langname
local current_headword_line
local status -- nil, 'under_headword', 'under_kanji', 'in_ja_readings'
wikicode = wikicode:gsub('\n*<br */?>\n*({{ja%-altread)', '%1')
-- in the local function `function format_definition` below,
-- insertion of `|hira=` affects `{{tlb}}`
-- (`{{tlb|ja|followed by a verb phrase|hira=によって}}`)
-- and then that bad wikitext is preprocessed
-- (`The parameter "hira" is not used by this template`).
-- we are not doing anything with `{{tlb}}` anyway,
-- so remove it
wikicode = wikicode:gsub(' *{{tlb.-}}', '')
local output = {}
for line in (wikicode:match("==" .. langname .. "==\n(.*)") or ""):gmatch"[^\n]+" do
if status == 'under_headword' then
if line:match'^#+[^#:*]' then
insert(output, {
def = line
:gsub('<ref>.-<.ref>', '')
:gsub('<ref .->.-<.ref>', '')
:gsub('<ref.-/>', '')
:gsub('{{attention|ja.-}}', '')
:gsub('{{senseid|ja.-}}', ''),
kanji_spellings =
line:match'{{ja%-def|' and split(line:match'{{ja%-def|([^}]+)', '|') or
line:match'<!%-%- kana only %-%->' and {} or
current_kanji_spellings,
kanji_spellings_with_labels = current_kanji_spellings_with_labels,
kana_spellings = current_kana_spellings,
historical_kana_spellings = current_historical_kana_spellings,
header = current_header,
headword_line = current_headword_line,
})
end
elseif status == 'under_kanji' then
if line:match'^#+[^#:*]' then
insert(output, {
def = '#' .. line
:gsub('<ref>.-<.ref>', '')
:gsub('<ref .->.-<.ref>', '')
:gsub('<ref.-/>', '')
:gsub('{{attention|ja.-}}', ''),
kanji_spellings = {},
kanji_spellings_with_labels = {},
kana_spellings = {},
historical_kana_spellings = {},
header = current_header,
headword_line = current_headword_line,
})
elseif line:match'^{{ja%-readings%f[|}%z]' then
output[#output].kanji_readings = {}
status = 'in_ja_readings'
end
elseif status == 'in_ja_readings' then
for rs in line:gmatch'|[a-z]+=([^|}]+)' do
for r in rs:gmatch'(..-)%f[,%z],?' do
r = r:gsub('%-', ''):match('..-%f[<%z]'):match'^%s*(.-)%s*$'
if not contains(output[#output].kanji_readings, r) then
insert(output[#output].kanji_readings, r:gsub('%-', ''):match'^%s*(.-)%s*$')
end
end
end
if line:match'}}' then status = 'under_kanji' end
end
-- the following branches are ordered by frequency; read backwards
if line:match'^{{ja%-noun[|}]' or line:match'^{{ja%-adj[|}]' or
line:match'^{{ja%-pos[|}]' or line:match'^{{ja%-phrase[|}]' or
line:match'^{{ja%-verb[|}]' or line:match'^{{ja%-verb form[|}]' or
line:match'^{{ja%-verb%-suru[|}]' or line:match'{{ja%-altread[|}]' or
line:match'{{ja%-adv[|}]' then
local escaped_line = line:gsub('%[%[([^%[%]|]-)|([^%[%]|]-)%]%]', '[[%1`%2]]'):gsub('|hhira=', '|hist='):gsub('|hkata=', '|hist=')
escaped_line = escaped_line:gsub('|hira=', '|') -- ja-altread
escaped_line = escaped_line:gsub('|kata=', '|') -- ja-altread
current_kana_spellings = map(gmatch_array(escaped_line, '|([・、' .. range.kana .. '\'%^%-%. %%]+)'), m_ja.remove_ruby_markup)
current_historical_kana_spellings = map(gmatch_array(escaped_line, '|hist=([' .. range.kana .. '\'%^%-%. %%]+)'), m_ja.remove_ruby_markup)
current_headword_line = line
status = 'under_headword'
elseif line:match'^{{ja%-kanji[|}]' then
current_kana_spellings = {}
current_historical_kana_spellings = {}
current_headword_line = line
insert(output, {
def = '#' .. data.grades[m_ja.kanji_grade(title)],
kanji_spellings = {},
kanji_spellings_with_labels = {},
kana_spellings = {},
historical_kana_spellings = {},
header = current_header,
headword_line = line,
})
status = 'under_kanji'
elseif line:match'^===+[^=]+===+$' then
local head_level_new, header_new = line:match'^(===+)([^=]+)===+$'
if not status or head_level_new:len() <= current_head_level:len() then
current_head_level, current_header = head_level_new, header_new
status = nil
end
elseif line:match("^{{ja%-kanjitab[|}]") then
local alt_argument = line:match("|alt=([^|}]+)")
current_kanji_spellings = alt_argument and split(alt_argument:gsub(':[^,]*', ''), ',') or {}
current_kanji_spellings_with_labels = alt_argument and split(alt_argument, ',') or {}
elseif line:match'^==[^=]+==$' then
break
end
end
return output
end
-- ditto, except that each definition also contains the title of the page it is from
local function get_definitions_from_entry(title)
local wikicode = getContent_memo(title)
local defs = get_definitions_from_wikicode(wikicode, title)
map(defs, function(def)
def.title = title
insert(({ Hira = true, Kana = true, ['Hira+Kana'] = true })[m_ja.script(title)] and def.kana_spellings or def.kanji_spellings, title)
end)
return defs
end
local function format_table_content(defs, frame, title, no_cat)
local function ruby(kanji, kana) -- this function ought to be in [[Module:ja]]
local kanji_segments = ugsub(kanji, "([" .. range.kanji .. range.ideograph .. range.latin .. range.numbers .. "]+)", "`%1`")
-- returns possible matches between kanji and kana
-- for example, match_k('`物`の`哀`れ', 'もののあわれ') returns { '[物](も)の[哀](のあわ)れ', '[物](もの)の[哀](あわ)れ' }
local function match_k(kanji_segments, kana)
if kanji_segments:find('`') then
local kana_portion, kanji_portion, rest = umatch(kanji_segments, '(.-)`(.-)`(.*)')
kana = umatch(kana, '^' .. kana_portion .. '(.*)')
if not kana then
return {}
end
local candidates = {}
for i = 1, ulen(kana) do
for _, candidate in ipairs(match_k(rest, usub(kana, i + 1))) do
insert(candidates, kana_portion .. '[' .. kanji_portion .. '](' .. usub(kana, 1, i) .. ')' .. candidate)
end
end
return candidates
else
return (kanji_segments == kana) and { kana } or {}
end
end
local matches = match_k(kanji_segments, kana)
local output = #matches == 1 and matches[1] or ('[' .. kanji .. '](' .. kana .. ')')
return output:gsub("%[([^%[%]]+)%]%(([^%(%)]+)%)", "<ruby><rb>%1</rb><rt>%2</rt></ruby>")
end
local function format_headword(defs)
local def_title = defs[1].title
local kana = defs[1].kana_spellings[1]
local headword = link_bracket(def_title, kana and pagename ~= kana and ruby(def_title, kana) or def_title)
local kanji_grade = ulen(def_title) == 1 and m_ja.kanji_grade(def_title)
return "<span style=\"font-size:x-large\">" .. headword .. "</span>" .. (kanji_grade and
"<span class=\"explain\" title=\"" .. data.grades[kanji_grade] .. "\" style=\"vertical-align: top;\">" .. data.grade_labels[kanji_grade] .. "</span>" or ""
)
end
local preprocess_memo = memoize(function (s) return frame:preprocess(s) end)
local function format_definitions(defs)
local headword_line_categories = {}
local alt_forms, alt_forms_rep = {}, { [title] = true, [defs[1].title] = true }
local kanji_readings
local function format_definition(def)
local cat_prefixes = {
["CAT"] = true,
["CATEGORY"] = true
}
local def_text = def.def:match'{{rfdef[|}]' and "''This term needs a translation to English.''" or preprocess_memo(def.def
:gsub('^#+ *', '')
--TODO: strip unwanted templates and parser functions (e.g. {{c}}). Will use wiki parser once ready, as this can get very complex.
)
:gsub("(%[%[[ _]*(%a-)[ _]*:.-%]%])", function(link, cat_prefix)
if cat_prefixes[cat_prefix:upper()] then
return ""
end
return link
end)
local def_prefix = def.def:match'^#+':gsub('#', ':')
local def_pos_label = ' <span style="padding-right:.6em;color:var(--wikt-palette-darkgrey, #54595d);font-size:80%">[' .. def.header:ulower() .. ']</span> '
if not no_cat and def.kana_spellings[1] then
local cat_hira, cat_kata = {}, {}
require'Module:Jpan-headword'.cat{
lang = lang,
pagename = title,
categories = cat_hira,
katakana_category = cat_kata,
pos = def.header:gsub('^.', string.ulower)
}
insert(headword_line_categories, format_cat(
cat_hira,
lang,
lang:makeSortKey(def.kana_spellings[1])
))
insert(headword_line_categories, format_cat(
cat_kata,
lang,
lang:makeSortKey(def.kana_spellings[1], Kana)
))
end
if def.kana_spellings[1] then alt_forms_rep[def.kana_spellings[1]] = true end
for _, s in ipairs(def.kanji_spellings) do
if not alt_forms_rep[s] then
alt_forms_rep[s] = true
insert(alt_forms, s)
end
end
for _, s in ipairs(def.kana_spellings) do
if not alt_forms_rep[s] then
alt_forms_rep[s] = true
insert(alt_forms, s)
end
end
kanji_readings = def.kanji_readings or kanji_readings
return def_prefix .. def_pos_label .. def_text
end
local formatted_defs = concat(map(defs, format_definition), '\n')
if #alt_forms == 1 and alt_forms[1] == title then alt_forms = {} end
return concat(headword_line_categories) .. '\n' .. formatted_defs
.. (#alt_forms > 0 and '\n: <div style="background-color:var(--wikt-palette-cream, #f8f8f0)"><span style="color:var(--wikt-palette-darkgrey, #54595d);font-size:80%">'
.. (#alt_forms == 1 and 'Alternative spelling' or 'Alternative spellings')
.. '</span><br><span style="margin-left:.8em">'
.. concat(map(alt_forms, link), ', ')
.. '</span></div>' or '')
.. (kanji_readings and '\n: <div style="background-color:var(--wikt-palette-cream, #f8f8f0)"><span style="color:var(--wikt-palette-darkgrey, #54595d);font-size:80%">Kanji reading:</span><br><span style="margin-left:.8em">'
.. concat(map(kanji_readings, link), ', ')
.. '</span></div>' or '')
end
local is_first_row = true
local function format_row(defs)
local output = '|-\n| style="white-space:nowrap;width:15%;vertical-align:top;' .. (is_first_row and '' or 'border-top:1px solid var(--wikt-palette-grey-4,lightgray);') .. '" | ' .. format_headword(defs)
.. '\n| style="' .. (is_first_row and '' or 'border-top:1px solid var(--wikt-palette-grey-4,lightgray);') .. '" |\n' .. format_definitions(defs) .. '\n'
is_first_row = false
return output
end
local def_groups = group(defs, function(def) return def.title .. ',' .. (def.kana_spellings[1] or '') end)
local rows = map(def_groups, format_row)
return '{| style="width: 100%"\n' .. concat(rows) .. '|}'
end
local function translit(title)
local sc = m_ja.script(title)
if sc == "Hira" or sc == "Kana" or sc == "Hira+Kana" then
return kana_to_romaji(title, "ja")
end
end
local function translit_kana(title)
return kana_to_romaji(title, "ja")
end
local redirect_type = {
-- auto-detected types
{
name = 'romaji',
article = 'the',
sc = "Latn",
display = function(title)
return title
end,
detect = function(title, defs)
if m_ja.script(title) ~= 'Romaji' then return {} end
local rom = title:ulower():gsub('[- ]', '')
return filter(defs, function(def)
for _, k in ipairs(def.kana_spellings) do
if rom == kana_to_romaji(k, "ja") then
insert(result, format_cat({langname .. " romanizations"}, lang))
return true
end
end
return false
end)
end,
no_cat = true,
},
{
name = 'hiragana spelling',
article = 'the',
translit = translit_kana,
detect = function(title, defs)
if m_ja.script(title) ~= 'Hira' then return {} end
local rom = kana_to_romaji(title, "ja"):ulower():gsub('[- ]', '')
return filter(defs, function(def)
if not contains(def.kana_spellings, title) and contains(def.historical_kana_spellings, title) then
return false
end
for _, k in ipairs(def.kana_spellings) do
if rom == kana_to_romaji(k, "ja") then
return true
end
end
return false
end)
end,
},
{
name = 'katakana spelling',
article = 'the',
translit = translit_kana,
detect = function(title, defs)
if m_ja.script(title) ~= 'Kana' then return {} end
local rom = kana_to_romaji(title, "ja"):ulower():gsub('[- ]', '')
return filter(defs, function(def)
if not contains(def.kana_spellings, title) and contains(def.historical_kana_spellings, title) then
return false
end
for _, k in ipairs(def.kana_spellings) do
if rom == kana_to_romaji(k, "ja") then
return true
end
end
return false
end)
end,
},
{
name = 'historical kana spelling',
article = 'a',
translit = function(title)
return kana_to_romaji(title, "ja", nil, {hist = true})
end,
detect = function(title, defs)
return filter(defs, function(def)
if not contains(def.kana_spellings, title) and contains(def.historical_kana_spellings, title) then
local sc = m_ja.script(title)
if sc == 'Hira' then
insert(result, format_cat(
{langname .. " historical hiragana"},
lang,
lang:makeSortKey(def.historical_kana_spellings[1], Hira)
))
elseif sc == 'Kana' then
insert(result, format_cat(
{langname .. " historical katakana"},
lang,
lang:makeSortKey(def.historical_kana_spellings[1], Kana)
))
elseif sc == 'Hira+Kana' then
insert(result, format_cat(
{langname .. " terms spelled with mixed historical kana"},
lang,
lang:makeSortKey(def.historical_kana_spellings[1], Hrkt)
))
end
return true
end
end)
end,
},
{
name = 'kyūjitai',
article = 'the',
detect = function(title, defs)
local shin = title:gsub('.[\128-\191]*', function(c)
if c == '辨' or c == '辯' or c == '瓣' then return '弁' end
return d_kyu[1]:match('(%S*)' .. c .. '%s') or c
end)
if shin == title then return {} end
return ulen(title) == 1 and filter(defs, function(def) return def.header == "Kanji" end) or filter(defs, function(def)
if shin == def.title then
if def.kana_spellings[1] then
insert(result, format_cat({langname .. " kyūjitai spellings"}, lang, lang:makeSortKey(def.kana_spellings[1])))
end
return true
end
end)
end,
display = function(title)
return ja(title:gsub('.[\128-\191]*', function(c)
return d_kyu[1]:match(c .. '(&#x%x+;)%s') or c
end))
end,
no_cat = true,
},
{
name = 'kyūjitai of an alternative spelling',
article = 'the',
detect = function(title, defs, key)
local shin = title:gsub('.[\128-\191]*', function(c)
if c == '辨' or c == '辯' or c == '瓣' then return '弁' end
return d_kyu[1]:match('(%S*)' .. c .. '%s') or c
end)
if shin == title then return {} end
return filter(defs, function(def)
if shin ~= def.title and contains(def.kanji_spellings, shin) and (not key or contains(def.kana_spellings, key)) then
if def.kana_spellings[1] then
insert(result, format_cat({langname .. " kyūjitai spellings"}, lang, lang:makeSortKey(def.kana_spellings[1])))
end
return true
end
end)
end,
display = function(title)
return ja(title:gsub('.[\128-\191]*', function(c)
return d_kyu[1]:match(c .. '(&#x%x+;)%s') or c
end))
end,
display_labels = function(title)
local shin = title:gsub('.[\128-\191]*', function(c)
if c == '辨' or c == '辯' or c == '瓣' then return '弁' end
return d_kyu[1]:match('(%S*)' .. c .. '%s') or c
end)
return '(' .. link(shin) .. ')'
end,
no_cat = true,
},
{
name = 'alternative spelling',
article = 'an',
detect = function(title, defs, key)
return filter(defs, function(def)
return contains(def.kanji_spellings, title) and (not key or contains(def.kana_spellings, key)) or contains(def.kana_spellings, title)
end)
end,
display_labels = function(title, defs)
for _, def in ipairs(defs) do for _, kl in ipairs(def.kanji_spellings_with_labels) do
local ks, lb = kl:match'^(.-):(.+)$'
if ks == title then
return require("Module:labels").show_labels { labels = split(lb, ' '), lang = lang }
end
end end
return ''
end,
is_fallback = true,
},
-- manual input only types
['vk'] = {
name = 'variant kanji form',
article = 'a',
detect = function(title, defs)
return ulen(title) == 1 and filter(defs, function(def)
return def.header == "Kanji"
end) or defs
end,
},
['iter'] = {
name = 'form with iteration marks',
article = 'a',
detect = function(_, defs)
return defs
end,
},
['niter'] = {
name = 'form without iteration marks',
article = 'a',
detect = function(_, defs)
return defs
end,
},
['eshin'] = {
name = 'extended shinjitai',
article = 'an',
detect = function(title, defs)
return ulen(title) == 1 and filter(defs, function(def)
return def.header == "Kanji"
end) or defs
end,
},
names = {
['rom'] = 1,
['hira'] = 2,
['kata'] = 3,
['hkana'] = 4,
['kyu'] = 5,
['kyualt'] = 6,
['alt'] = 7,
['vk'] = 'vk',
['iter'] = 'iter',
['niter'] = 'niter',
['eshin'] = 'eshin',
},
}
function export.show(frame)
local title = pagename
local args = require("Module:parameters").process(frame:getParent().args, {
[1] = { list = true },
['term'] = { list = true, allow_holes = true, separate_no_index = true },
['type'] = { list = true, allow_holes = true, separate_no_index = true },
['key'] = { list = true, allow_holes = true },
})
result = {
'{| class="wikitable ja-see" style="min-width:70%"\n|-\n| <b>',
'For pronunciation and definitions of ', ja(title), ' – see the following entr',
#args[1] > 1 and 'ies' or 'y',
'.',
'</b>',
}
local bad_redirects = {}
local name_count = 0
local name_previous, rt_previous
local function make_footnote(term)
insert(result, '\n|-\n| (This term, ')
insert(result, require("Module:links").full_link({
lang = lang,
sc = rt_previous.sc and require("Module:scripts").getByCode(rt_previous.sc) or nil,
alt = (rt_previous.display or ja)(title),
tr = (rt_previous.translit or translit)(term or title) or nil
}, "term"))
insert(result, ', is ')
insert(result, name_previous)
insert(result, ' of the above term')
if name_count > 1 then insert(result, 's') end
insert(result, '.)')
end
local function add_to_result(name, rt, defs, term)
if rt.display_labels then
name = name .. ' ' .. rt.display_labels(title, defs)
end
if name_previous and name_previous ~= name then
make_footnote(term)
name_count = 1
else
name_count = name_count + 1
end
name_previous = name
rt_previous = rt
insert(result, '\n|-\n| style="background-color:var(--wikt-palette-white,#ffffff);color:inherit" |\n')
insert(result, format_table_content(defs, frame, title, rt.no_cat))
end
for i_lemma, lemma in ipairs(args[1]) do
local defs = get_definitions_from_entry(lemma)
--mw.logObject(defs) --use this to inspect "defs"
local label_term = args.term[i_lemma] or args.term.default
local label_lemma = args.type[i_lemma] or args.type.default
if label_lemma then
local ll1, ll2 = label_lemma:match'^(.-)(%S*)$'
local rt = redirect_type[redirect_type.names[ll2]]
if rt then
defs = rt.detect(title, defs)
if #defs > 0 then
-- This code deals with the redirect type description's articles like "the hiragana", "a rare hiragana", "an unusual hiragana" when there is a manual input |type=....
add_to_result((label_lemma:match'^an? ' or label_lemma:match'^the ') and ll1 .. rt.name or rt.article .. ' ' .. ll1 .. rt.name, rt, defs, label_term)
else
insert(bad_redirects, lemma)
end
else
add_to_result(label_lemma, {}, defs, label_term)
end
else
local success = false
for _, rt in ipairs(redirect_type) do
if not (success and rt.is_fallback) then
local defs_try = rt.detect(title, defs, args.key[i_lemma])
if #defs_try > 0 then
success = true
add_to_result(rt.article .. ' ' .. rt.name, rt, defs_try, label_term)
end
end
end
if not success then
insert(bad_redirects, lemma)
end
end
end
if name_previous then
make_footnote(args.term.default)
if new_title(langname .. ' kanji read as ' .. title, 14).exists then
insert(result, '<br><span style="font-size:85%;">For a list of all kanji read as ')
insert(result, ja(title))
insert(result, ', see ')
insert(result, '[[:Category:' .. langname .. ' kanji read as ')
insert(result, title)
insert(result, ']].</span>')
end
insert(result, '\n|}')
else -- failure to find any definitions
result[6] = ': ' .. concat(map(args[1], function(title) return '<span style="font-size:120%">' .. link(title) .. '</span>' end), ', ')
insert(result, '\n|}')
end
if #bad_redirects > 0 then
insert(result, '\n<small class="attentionseeking maintenance-line">(The following ' .. (#bad_redirects == 1 and 'entry does' or 'entries do') .. ' not have a page created for ' .. (#bad_redirects == 1 and 'it' or 'them') .. ' yet: ')
insert(result, concat(map(bad_redirects, link), ", "))
insert(result, '.)</small>')
insert(result, format_cat({langname .. " redlinks/ja-see"}, lang))
end
-- Standard maintenance categories usually done by [[Module:headword]].
local lang_cats, page_cats = {}, {}
maintenance_cats(headword_data.page, lang, lang_cats, page_cats)
insert(result, format_cat(lang_cats, lang))
insert(result, format_cat(page_cats, nil, "-"))
return concat(result)
end
function export.show_kango(frame) -- to be removed
return export.show(frame, 'kango')
end
function export.show_gv(frame) -- to be removed
return export.show(frame, 'glyphvar')
end
return export
73x5kg89tfoy05wgj400hs8an46eko6
Modul:ja/data/kyu
828
1358625
6334396
2026-08-17T19:24:00Z
Wikihez
7344
[[:en:Module:ja/data/kyu]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6334396
Scribunto
text/plain
-- part 1: Joyo official 新字体 - 旧字体
-- part 2: Joyo kyujitai unlisted 新字体 - 旧字体
-- part 3: Joyo extended 拡張新字体 - 常用漢字字体
-- part 4: Jimeyo official 新字体 - 旧字体
-- part 5: Jimeyo kyujitai unlisted 新字体 - 旧字体
-- part 6: Hyogai 簡易慣用字体 - 印刷標準字体
-- part 7: Hyogai kyujitai unlisted 印刷標準字体(新字体) - 旧字体
-- part 8: Hyogai extended 拡張新字体 - 印刷標準字体
-- part 9: Joyo 新字体 - 旧字体 (unsupported title)
-- part 10: Jimeyo 新字体 - 旧字体 (unsupported title)
return {[[
亜亞 悪惡 圧壓 囲圍 医醫 為爲 壱壹 隠隱 栄榮 営營
衛衞 駅驛 円圓 塩鹽 縁緣 艶艷 応應 欧歐 殴毆 桜櫻
奥奧 横橫 温溫 穏穩 仮假 価價 画畫 会會 絵繪 壊壞
懐懷 概槪 拡擴 殻殼 覚覺 学學 岳嶽 楽樂 渇渴 缶罐
巻卷 陥陷 勧勸 寛寬 関關 歓歡 観觀 気氣 帰歸 亀龜
偽僞 戯戲 犠犧 旧舊 拠據 挙擧 虚虛 峡峽 挟挾 狭狹
郷鄕 暁曉 区區 駆驅 勲勳 薫薰 径徑 茎莖 恵惠 掲揭
渓溪 経經 蛍螢 軽輕 継繼 鶏鷄 芸藝 撃擊 欠缺 研硏
県縣 倹儉 剣劍 険險 圏圈 検檢 献獻 権權 顕顯 験驗
厳嚴 戸戶 広廣 効效 恒恆 黄黃 鉱鑛 号號 国國 黒黑
済濟 斎齋 砕碎 剤劑 雑雜 参參 桟棧 蚕蠶 惨慘 賛贊
残殘 糸絲 歯齒 児兒 辞辭 湿濕 実實 写寫 釈釋 寿壽
収收 従從 渋澁 獣獸 縦縱 粛肅 処處 緒緖 叙敍 将將
称稱 渉涉 焼燒 証證 奨奬 条條 状狀 乗乘 浄淨 剰剩
畳疊 縄繩 壌壤 嬢孃 譲讓 醸釀 触觸 嘱囑 真眞 寝寢
慎愼 尽盡 図圖 粋粹 酔醉 穂穗 随隨 髄髓 枢樞 数數
瀬瀨 声聲 斉齊 静靜 窃竊 摂攝 専專 浅淺 戦戰 践踐
銭錢 潜潛 繊纖 禅禪 双雙 壮壯 争爭 荘莊 捜搜 挿插
巣巢 曽曾 痩瘦 装裝 総總 騒騷 増增 蔵藏 臓臟 即卽
属屬 続續 堕墮 対對 体體 帯帶 滞滯 台臺 滝瀧 択擇
沢澤 担擔 単單 胆膽 団團 断斷 弾彈 遅遲 痴癡 虫蟲
昼晝 鋳鑄 庁廳 徴徵 聴聽 鎮鎭 逓遞 鉄鐵 点點 転轉
伝傳 灯燈 当當 党黨 盗盜 稲稻 闘鬭 徳德 独獨 読讀
届屆 弐貳 悩惱 脳腦 覇霸 拝拜 廃廢 売賣 麦麥 発發
髪髮 抜拔 晩晚 蛮蠻 秘祕 浜濱 瓶甁 払拂 仏佛 併倂
並竝 餅餠 辺邊 変變 歩步 宝寶 豊豐 褒襃 翻飜 毎每
万萬 満滿 麺麵 黙默 弥彌 訳譯 薬藥 与與 予豫 余餘
誉譽 揺搖 様樣 謡謠 来來 頼賴 乱亂 覧覽 竜龍 両兩
猟獵 緑綠 涙淚 塁壘 礼禮 励勵 戻戾 霊靈 齢齡 暦曆
歴歷 恋戀 錬鍊 炉爐 労勞 郎郞 楼樓 録錄 湾灣
飲飮 鋭銳 閲閱 悦悅 顔顏 告吿 舎舍 尚尙 呉吳 娯娛
告吿 歳歲 産產 青靑 清淸 税稅 説說 絶絕 脱脫 内內
舗舖
剥剝 頬頰 填塡 篭籠
亘亙 凜凛 尭堯 巌巖 桧檜 槙槇 祢禰 祷禱 禄祿 穣穰 遥遙
聡聰 彦彥
唖啞 頴穎 鴎鷗 撹攪 麹麴 鹸鹼 噛嚙 繍繡 蒋蔣 醤醬 掻搔
祷禱 桝枡 屏屛 并幷 蝋蠟 沪濾 芦蘆 弯彎
讃讚
嘘噓 焔焰 躯軀 屡屢 莱萊 騨驒 蝉蟬 箪簞 掴摑 顛顚 涜瀆
嚢囊 溌潑 醗醱 繋繫 蛎蠣 竃竈 潅灌 諌諫 頚頸 賎賤 壷壺
砺礪 梼檮 涛濤 迩邇 蝿蠅 侭儘 薮藪 褝襌
逸逸 謁謁 禍禍 悔悔 海海
慨慨 喝喝 褐褐 漢漢 祈祈
既既 器器 響響 勤勤 謹謹
穀穀 殺殺 祉祉 視視 社社
者者 煮煮 臭臭 祝祝 暑暑
署署 諸諸 祥祥 神神 節節
祖祖 僧僧 層層 憎憎 贈贈
嘆嘆 著著 懲懲 塚塚 都都
突突 難難 梅梅 繁繁 卑卑
碑碑 賓賓 頻頻 敏敏 侮侮
福福 塀塀 勉勉 墨墨 免免
欄欄 隆隆 虜虜 類類 練練
朗朗 廊廊
渚渚 猪猪 琢琢 祐祐 禎禎
]]}
-- ref part 1: Joyo 新字体 - 旧字体 (ambiguous)
--[[
highly ambiguous:
弁 弁辨瓣辯
other (variant character/very rare, treated as part 1):
缶 缶罐
既 既旣
芸 芸藝
欠 欠缺
糸 糸絲
証 証證
体 体體
台 台臺
灯 灯燈
虫 虫蟲
闘 闘鬭
浜 浜濱
予 予豫
余 余餘
礼 礼禮
缶 缶罐
既 既旣
]]
-- ref part 2: Joyo "情報機器に搭載されている印刷文字字体の関係で"
--[[
餌・遡・遜・謎・餅・淫・葛・僅・煎・詮・嘲・捗・溺・塡・賭・剝・箸・蔽・頰
]]
-- ref part 3: unofficial or disputed (seen as variant forms rather than old forms)
--[[
Joyo:
*回囘 *却卻 *糾糺 *間閒 *強强 *教敎 *群羣 *携攜 *冴冱
*冊册 *床牀 *姉姊 *刃刄 *跡蹟 *窓窗 *勅敕 *妊姙 *粘黏
*姫姬 *没沒 *欲慾 *鎌鐮 *略畧 *隣鄰 *隷隸
*契契 *着著 *旅旅 *廉廉 *羽羽
*益益 *館館 *飼飼 *晴晴 *精精
*飯飯
Jimeyo (not kyujitai):
**晃晄 **萌萠
Jimeyo (not listed):
***晋晉
Hyogai:
***瑶瑤
]]
f0q6m14e8pyqluoe6iczgz8zni21y8k
Şablon:R:cel:EDPC/pageurl
10
1358626
6334408
2026-08-17T21:35:17Z
Wikihez
7344
[[:en:Template:R:cel:EDPC/pageurl]] hat kopîkirin ([[Bikarhêner:Wikihez/skrîpt/js/kopi-enWikt.js|kopi-enWikt.js]])
6334408
wikitext
text/x-wiki
{{#if:{{{1|}}}|{{#switch:{{{1|<noinclude>300</noinclude>}}}|290=|291=|{{#ifexpr:{{{1|<noinclude>300</noinclude>}}}>291|n{{#expr:{{{1|<noinclude>300</noinclude>}}}+2}}|n{{#expr:{{{1|<noinclude>300</noinclude>}}}+4}}}}}}|}}
d5h3xntwfz9gqyx8t8utah8e34w3fy5
Şablon:Jêgirtin/Ahmet Öner/Di edebiyata kurdî ya gelêrî da di’a û nifir/2017
10
1358627
6334421
2026-08-17T23:00:43Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ahmet Öner/Di edebiyata kurdî ya gelêrî da di’a û nifir/2017]] weke [[Şablon:J:Öner:2017]] guhart
6334421
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Öner:2017]]
nhq7yi27ax7yjogftrdj6d12axv12b8
Şablon:Jêgirtin/Albert Camus/Biyanî
10
1358628
6334429
2026-08-17T23:06:23Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Albert Camus/Biyanî]] weke [[Şablon:J:Albert Camus:Biyanî]] guhart
6334429
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Albert Camus:Biyanî]]
phorqvd5cxcp5m4ya1b865b1ecjgxdt
Şablon:Jêgirtin/Antoine de Saint-Exupéry/Mîrzayê Biçûk
10
1358629
6334434
2026-08-17T23:10:13Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Antoine de Saint-Exupéry/Mîrzayê Biçûk]] weke [[Şablon:J:Antoine de Saint-Exupéry:Mîrzayê Biçûk]] guhart
6334434
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Antoine de Saint-Exupéry:Mîrzayê Biçûk]]
fu8w0frqv2ona9n03zn8jd67d7ndvvu
Şablon:Jêgirtin/Anton Çexov/Bexçeyê Vîşne
10
1358630
6334441
2026-08-17T23:15:28Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Anton Çexov/Bexçeyê Vîşne]] weke [[Şablon:J:Anton Çexov:Bexçeyê Vîşne]] guhart
6334441
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Anton Çexov:Bexçeyê Vîşne]]
hb4l19cqizhvc2wxicsg8bzx1bjumhz
Şablon:Jêgirtin/Bavê Nazê/Hêsir û Baran/1986
10
1358631
6334444
2026-08-17T23:16:46Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Bavê Nazê/Hêsir û Baran/1986]] weke [[Şablon:J:Bavê Nazê:1986]] guhart
6334444
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Bavê Nazê:1986]]
rcw166mrh3jwbgc9sy9rtewxx5ob9hz
Şablon:Jêgirtin/Bûbê Eser/Gardiyan/2002
10
1358632
6334450
2026-08-17T23:21:09Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Bûbê Eser/Gardiyan/2002]] weke [[Şablon:J:Bûbê Eser:Gardiyan:2002]] guhart
6334450
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Bûbê Eser:Gardiyan:2002]]
ji5bb7fjpo4iwihfcn5gw9dka5nan4e
Şablon:Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 1 Hejmar 1-23 (1932-1933)/1998
10
1358633
6334455
2026-08-17T23:26:35Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 1 Hejmar 1-23 (1932-1933)/1998]] weke [[Şablon:J:Hawar:1998:1]] guhart
6334455
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hawar:1998:1]]
h3k70ad86ofq8j6pnba7bpk8fk15zan
Şablon:Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998
10
1358634
6334458
2026-08-17T23:29:05Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Celadet Alî Bedirxan/HAWAR Cild 2 Hejmar 24-57 (1934-1943)/1998]] weke [[Şablon:Hawar:1998:2]] guhart
6334458
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:Hawar:1998:2]]
estv520vibshqptc5znkzlwd6xa09qq
6334461
6334458
2026-08-17T23:29:33Z
Wikihez
7344
Hedefa beralîkirinê ji [[Şablon:Hawar:1998:2]] ber bi [[Şablon:J:Hawar:1998:2]] ve guhart
6334461
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hawar:1998:2]]
hx2se7npktnc76e9ayq2njhy95psm1o
Şablon:Hawar:1998:2
10
1358635
6334460
2026-08-17T23:29:20Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Hawar:1998:2]] weke [[Şablon:J:Hawar:1998:2]] guhart
6334460
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hawar:1998:2]]
hx2se7npktnc76e9ayq2njhy95psm1o
Şablon:Jêgirtin/Ciwanmerd Kulek/Zemanekî li Sê Welatan
10
1358636
6334463
2026-08-17T23:37:18Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ciwanmerd Kulek/Zemanekî li Sê Welatan]] weke [[Şablon:J:Ciwanmerd Kulek:Zemanekî li Sê Welatan]] guhart
6334463
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Ciwanmerd Kulek:Zemanekî li Sê Welatan]]
2tttdvgg4b1hf37cckeyxkltnsgrk3t
Şablon:Jêgirtin/Egîdê Xudo/Dê û Dêmarî
10
1358637
6334470
2026-08-17T23:44:52Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Egîdê Xudo/Dê û Dêmarî]] weke [[Şablon:J:Egîdê Xudo:Dê û Dêmarî:1995]] guhart
6334470
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Egîdê Xudo:Dê û Dêmarî:1995]]
czx1ddaltzgklxjr3a63zwgt2mm7fhg
Şablon:Jêgirtin/Ehmed Huseynî/Bi xewna we pênûsê dilorînim/2002
10
1358638
6334473
2026-08-17T23:46:50Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ehmed Huseynî/Bi xewna we pênûsê dilorînim/2002]] weke [[Şablon:J:Ehmed Huseynî:Bi xewna we pênûsê dilorînim:2002]] guhart
6334473
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Ehmed Huseynî:Bi xewna we pênûsê dilorînim:2002]]
hkzanh7barw4znmsva5kpj4huz81fhh
Şablon:Jêgirtin/Ehmedê Xanî/Dîwana Ehmedê Xanî
10
1358639
6334479
2026-08-17T23:53:00Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ehmedê Xanî/Dîwana Ehmedê Xanî]] weke [[Şablon:J:Ehmedê Xanî:Dîwana Ehmedê Xanî]] guhart
6334479
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Ehmedê Xanî:Dîwana Ehmedê Xanî]]
hs7yf3g2586fmtncey0dpllrmme5fcz
Şablon:Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan
10
1358640
6334482
2026-08-17T23:54:23Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan]] weke [[Şablon:J:Nûbihara Biçûkan]] guhart
6334482
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Nûbihara Biçûkan]]
hdhs4ycuo7h8am4td8kurfjwop6c6bo
Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan
11
1358641
6334484
2026-08-17T23:54:23Z
Wikihez
7344
Wikihezî/ê navê [[Gotûbêja şablonê:Jêgirtin/Ehmedê Xanî/Nûbihara Biçûkan]] weke [[Gotûbêja şablonê:J:Nûbihara Biçûkan]] guhart
6334484
wikitext
text/x-wiki
#BERALÎKIRIN [[Gotûbêja şablonê:J:Nûbihara Biçûkan]]
7yiwzcp0nyhyofc6of0nb7mk46kywul
Şablon:Jêgirtin/Mele Xelefê Bafê/Dîroka kelepora mîrnişîniya Botan/2004
10
1358642
6334494
2026-08-18T00:17:01Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Mele Xelefê Bafê/Dîroka kelepora mîrnişîniya Botan/2004]] weke [[Şablon:J:Mele Xelefê Bafê:Dîroka kelepora mîrnişîniya Botan:2004]] guhart
6334494
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mele Xelefê Bafê:Dîroka kelepora mîrnişîniya Botan:2004]]
smgh27pe73xnvhoal3pzlbrqksxih1k
Şablon:Jêgirtin/Fawaz Husên/Siwarên Êşê/1994
10
1358643
6334500
2026-08-18T00:20:34Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Fawaz Husên/Siwarên Êşê/1994]] weke [[Şablon:J:Fawaz Husên:Siwarên Êşê:1994]] guhart
6334500
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Fawaz Husên:Siwarên Êşê:1994]]
bclea59ybuu3wrbjozwvxr5wgprcdct
Gotûbêja şablonê:Jêgirtin/Fawaz Husên/Siwarên Êşê/1994
11
1358644
6334502
2026-08-18T00:20:35Z
Wikihez
7344
Wikihezî/ê navê [[Gotûbêja şablonê:Jêgirtin/Fawaz Husên/Siwarên Êşê/1994]] weke [[Gotûbêja şablonê:J:Fawaz Husên:Siwarên Êşê:1994]] guhart
6334502
wikitext
text/x-wiki
#BERALÎKIRIN [[Gotûbêja şablonê:J:Fawaz Husên:Siwarên Êşê:1994]]
l59zvxdyssx4gprdsbza1jmuub8gjsj
Şablon:Jêgirtin/Feqiyê Teyran
10
1358645
6334507
2026-08-18T00:24:06Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Feqiyê Teyran]] weke [[Şablon:J:Feqiyê Teyran]] guhart
6334507
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Feqiyê Teyran]]
gnr4x2xghihkx424t74llj800d9sqq1
Şablon:Jêgirtin/Firat Cewerî/Girtî
10
1358646
6334509
2026-08-18T00:24:35Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Girtî]] weke [[Şablon:J:Firat Cewerî:Girtî:1986]] guhart
6334509
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Firat Cewerî:Girtî:1986]]
02jaupb4lrwt5xccm1f7yfvdw1t586b
Şablon:Jêgirtin/Firat Cewerî/Kevoka Spî/1996
10
1358647
6334513
2026-08-18T00:28:11Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Kevoka Spî/1996]] weke [[Şablon:J:Firat Cewerî:Kevoka Spî:1996]] guhart
6334513
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Firat Cewerî:Kevoka Spî:1996]]
gpcaee28vzossuwwjf7piqdz7g2j54v
Şablon:Jêgirtin/Firat Cewerî/Kultur, huner û edebiyat/1996
10
1358648
6334517
2026-08-18T00:33:13Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Kultur, huner û edebiyat/1996]] weke [[Şablon:J:Firat Cewerî:Kultur, huner û edebiyat:1996]] guhart
6334517
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Firat Cewerî:Kultur, huner û edebiyat:1996]]
khzisewvujbbqofgextaho1urs43uyn
Şablon:Jêgirtin/Firat Cewerî/Payiza Dereng
10
1358649
6334521
2026-08-18T00:35:55Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Payiza Dereng]] weke [[Şablon:J:Firat Cewerî:Payiza Dereng]] guhart
6334521
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Firat Cewerî:Payiza Dereng]]
6co4jc8ksufnvcycgsdi137f6ztvbyd
Şablon:Jêgirtin/Firat Cewerî/Çîroka Malbata Evdo/1999
10
1358650
6334579
2026-08-18T11:15:51Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Firat Cewerî/Çîroka Malbata Evdo/1999]] weke [[Şablon:J:Firat Cewerî:Çîroka Malbata Evdo:1999]] guhart: Bot: Rûpel bar kir
6334579
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Firat Cewerî:Çîroka Malbata Evdo:1999]]
pcvbmqnievy0x9scyot7awhe8y81dda
Şablon:Jêgirtin/Frîda Hecî Cewarî/Bîranînên Min
10
1358651
6334581
2026-08-18T11:15:54Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Frîda Hecî Cewarî/Bîranînên Min]] weke [[Şablon:J:Frîda Hecî Cewarî:Bîranînên Min]] guhart: Bot: Rûpel bar kir
6334581
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Frîda Hecî Cewarî:Bîranînên Min]]
49aicsn3qxiqe5n0uw80s87yotucsno
Şablon:Jêgirtin/Fêrîkê Ûsiv/Pampa Min/2009
10
1358652
6334583
2026-08-18T11:15:56Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Fêrîkê Ûsiv/Pampa Min/2009]] weke [[Şablon:J:Fêrîkê Ûsiv:Pampa Min:2009]] guhart: Bot: Rûpel bar kir
6334583
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Fêrîkê Ûsiv:Pampa Min:2009]]
ddmke428qy8a8rhgaf77o5pd1y0alce
Şablon:Jêgirtin/Hesen Huseyîn Denîz/Ro û Zê
10
1358653
6334585
2026-08-18T11:15:58Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesen Huseyîn Denîz/Ro û Zê]] weke [[Şablon:J:Hesen Huseyîn Denîz:Ro û Zê]] guhart: Bot: Rûpel bar kir
6334585
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hesen Huseyîn Denîz:Ro û Zê]]
jsgfu27o647dg2vi5eo7fp95ytw5qx3
Şablon:Jêgirtin/Hesen Qizilcî/Kenê Parsek
10
1358654
6334587
2026-08-18T11:16:00Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesen Qizilcî/Kenê Parsek]] weke [[Şablon:J:Hesen Qizilcî:Kenê Parsek]] guhart: Bot: Rûpel bar kir
6334587
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hesen Qizilcî:Kenê Parsek]]
hexva7a2xix4f5zt7fn0pe93iktm8pz
Şablon:Jêgirtin/Hesenê Metê/Epîlog
10
1358655
6334589
2026-08-18T11:16:02Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesenê Metê/Epîlog]] weke [[Şablon:J:Hesenê Metê:Epîlog]] guhart: Bot: Rûpel bar kir
6334589
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hesenê Metê:Epîlog]]
rw74r7mtbg0elj3yndbkf49t61xw80z
Şablon:Jêgirtin/Hesenê Metê/Labîrenta Cinan/1994
10
1358656
6334591
2026-08-18T11:16:03Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesenê Metê/Labîrenta Cinan/1994]] weke [[Şablon:J:Hesenê Metê:Labîrenta Cinan:1994]] guhart: Bot: Rûpel bar kir
6334591
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hesenê Metê:Labîrenta Cinan:1994]]
kwe3rat79gwlb24fq7xex5ttlplce5u
Şablon:Jêgirtin/Hesenê Metê/Smirnoff/1991
10
1358657
6334593
2026-08-18T11:16:04Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Hesenê Metê/Smirnoff/1991]] weke [[Şablon:J:Hesenê Metê:Smirnoff:1991]] guhart: Bot: Rûpel bar kir
6334593
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Hesenê Metê:Smirnoff:1991]]
cjfthnl45bt469dk3e08cs6jklxqgjz
Şablon:Jêgirtin/Ibrahîm Seydo Aydogan/Reş û Spî/2002
10
1358658
6334595
2026-08-18T11:16:06Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Ibrahîm Seydo Aydogan/Reş û Spî/2002]] weke [[Şablon:J:Ibrahîm Seydo Aydogan:Reş û Spî:2002]] guhart: Bot: Rûpel bar kir
6334595
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Ibrahîm Seydo Aydogan:Reş û Spî:2002]]
i03e5qu8rs129ttp64n8yyzzuzx7gwh
Şablon:Jêgirtin/Jan Dost/Mijabad
10
1358659
6334597
2026-08-18T11:16:07Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Jan Dost/Mijabad]] weke [[Şablon:J:Jan Dost:Mijabad]] guhart: Bot: Rûpel bar kir
6334597
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Jan Dost:Mijabad]]
l1e9283t5phw3cdq8paj0gc1fme2o4l
Şablon:Jêgirtin/K. Mezher Ehmed/Çend Rûpel Ji Dîroka Gelê Kurd
10
1358660
6334599
2026-08-18T11:16:08Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/K. Mezher Ehmed/Çend Rûpel Ji Dîroka Gelê Kurd]] weke [[Şablon:J:K. Mezher Ehmed:Çend Rûpel Ji Dîroka Gelê Kurd]] guhart: Bot: Rûpel bar kir
6334599
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:K. Mezher Ehmed:Çend Rûpel Ji Dîroka Gelê Kurd]]
a1nm8qicg2tfkn811210hfe66di6ktc
Şablon:Jêgirtin/M. Elî Kut/Mehkûm/2002
10
1358661
6334601
2026-08-18T11:16:10Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/M. Elî Kut/Mehkûm/2002]] weke [[Şablon:J:M. Elî Kut:Mehkûm:2002]] guhart: Bot: Rûpel bar kir
6334601
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:M. Elî Kut:Mehkûm:2002]]
b43mmgz406xka8fc4aw4xxvm9k9de7k
Şablon:Jêgirtin/M. Malmîsanij/Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû
10
1358662
6334603
2026-08-18T11:16:11Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/M. Malmîsanij/Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû]] weke [[Şablon:J:M. Malmîsanij:Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû]] guhart: Bot: Rûpel bar kir
6334603
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:M. Malmîsanij:Ji bo Rastnivîsînê Ferhenga Kurdî (Kurmancî)-Tirkî ya Nû]]
rpnapfzl3aj57dcdnm28unybglbmlmd
Şablon:Jêgirtin/M. Xalid Sadinî/Mela Huseynê Bateyî
10
1358663
6334605
2026-08-18T11:16:13Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/M. Xalid Sadinî/Mela Huseynê Bateyî]] weke [[Şablon:J:M. Xalid Sadinî:Mela Huseynê Bateyî]] guhart: Bot: Rûpel bar kir
6334605
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:M. Xalid Sadinî:Mela Huseynê Bateyî]]
9h8dsd3pdtd2lel0cm50suac3fy4okc
Şablon:Jêgirtin/Mahmûd Lewendî/Compûtera ber dilê min/2002
10
1358664
6334607
2026-08-18T11:16:14Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mahmûd Lewendî/Compûtera ber dilê min/2002]] weke [[Şablon:J:Mahmûd Lewendî:Compûtera ber dilê min:2002]] guhart: Bot: Rûpel bar kir
6334607
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mahmûd Lewendî:Compûtera ber dilê min:2002]]
85jdhrr8u6p9koif67s1sjv0fj4lrhj
Şablon:Jêgirtin/Mehmed Uzun
10
1358665
6334609
2026-08-18T11:16:18Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun]] weke [[Şablon:J:Mehmed Uzun]] guhart: Bot: Rûpel bar kir
6334609
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun]]
qv31lswkbrioa1hxho8szakg4sb56yy
Şablon:Jêgirtin/Mehmed Uzun/Bîra qederê/2002
10
1358671
6334621
2026-08-18T11:16:23Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Bîra qederê/2002]] weke [[Şablon:J:Mehmed Uzun:Bîra qederê:2002]] guhart: Bot: Rûpel bar kir
6334621
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun:Bîra qederê:2002]]
l5c5mxjybrfron6h5yr1x7hp9ybunxe
Şablon:Jêgirtin/Mehmed Uzun/Hawara Dîcleyê 1/2003
10
1358672
6334623
2026-08-18T11:16:25Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Hawara Dîcleyê 1/2003]] weke [[Şablon:J:Mehmed Uzun:Hawara Dîcleyê 1:2003]] guhart: Bot: Rûpel bar kir
6334623
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun:Hawara Dîcleyê 1:2003]]
66h2l39di0hkqolarb0fnxrxv9a5low
Şablon:Jêgirtin/Mehmed Uzun/Hêz û Bedewiya Pênûsê/1993
10
1358673
6334625
2026-08-18T11:16:27Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Hêz û Bedewiya Pênûsê/1993]] weke [[Şablon:J:Mehmed Uzun:Hêz û Bedewiya Pênûsê:1993]] guhart: Bot: Rûpel bar kir
6334625
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun:Hêz û Bedewiya Pênûsê:1993]]
h3qycpgg4lapmnas0c7lb8cqntsirh3
Şablon:Jêgirtin/Mehmed Uzun/Mirina Kalekî Rind/1987
10
1358674
6334627
2026-08-18T11:16:28Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Mirina Kalekî Rind/1987]] weke [[Şablon:J:Mehmed Uzun:Mirina Kalekî Rind:1987]] guhart: Bot: Rûpel bar kir
6334627
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun:Mirina Kalekî Rind:1987]]
afcg54sxkvu2dupspviy0cogn0n65bc
Şablon:Jêgirtin/Mehmed Uzun/Siya Evînê/1989
10
1358675
6334629
2026-08-18T11:16:31Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mehmed Uzun/Siya Evînê/1989]] weke [[Şablon:J:Mehmed Uzun:Siya Evînê:1989]] guhart: Bot: Rûpel bar kir
6334629
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun:Siya Evînê:1989]]
sunplp13kd7q3zv36yz4nt3a2hfly7g
Şablon:Jêgirtin/Mela Hisênê Bateyî/Mewlûda Kurdî
10
1358676
6334631
2026-08-18T11:16:33Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mela Hisênê Bateyî/Mewlûda Kurdî]] weke [[Şablon:J:Mela Hisênê Bateyî:Mewlûda Kurdî]] guhart: Bot: Rûpel bar kir
6334631
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mela Hisênê Bateyî:Mewlûda Kurdî]]
mx4107nq7bucawuouyfmyoud6xwsqim
Şablon:Jêgirtin/Melayê Cizirî/Dîwan/2004
10
1358677
6334633
2026-08-18T11:16:34Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Melayê Cizirî/Dîwan/2004]] weke [[Şablon:J:Melayê Cizirî:Dîwan:2004]] guhart: Bot: Rûpel bar kir
6334633
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Melayê Cizirî:Dîwan:2004]]
jhi499hpw4ncfoe8rtxhzjrz9wt8kuo
Şablon:Jêgirtin/Melayê Cizîrî/Dîwana Melayê Cizîrî
10
1358678
6334635
2026-08-18T11:16:36Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Melayê Cizîrî/Dîwana Melayê Cizîrî]] weke [[Şablon:J:Melayê Cizîrî:Dîwana Melayê Cizîrî]] guhart: Bot: Rûpel bar kir
6334635
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Melayê Cizîrî:Dîwana Melayê Cizîrî]]
tnoqf4ecc4zmqht7lubtjxky2fszjx1
Şablon:Jêgirtin/Mele Xelîlê Sêrtî/Nehc-ul Enam/1988
10
1358679
6334637
2026-08-18T11:16:37Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mele Xelîlê Sêrtî/Nehc-ul Enam/1988]] weke [[Şablon:J:Mele Xelîlê Sêrtî:Nehc-ul Enam:1988]] guhart: Bot: Rûpel bar kir
6334637
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mele Xelîlê Sêrtî:Nehc-ul Enam:1988]]
5oqy8wxlvocwcur8r7hcen72pzudci4
Şablon:Jêgirtin/Mustafa Aydogan/Berê gotin hebû/2002
10
1358680
6334639
2026-08-18T11:16:39Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mustafa Aydogan/Berê gotin hebû/2002]] weke [[Şablon:J:Mustafa Aydogan:Berê gotin hebû:2002]] guhart: Bot: Rûpel bar kir
6334639
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mustafa Aydogan:Berê gotin hebû:2002]]
amrgwsoxfyd2gybg5z6bixdy11asoxz
Şablon:Jêgirtin/Mîran Janbar/Qolyeya Cecileê
10
1358681
6334641
2026-08-18T11:16:46Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Mîran Janbar/Qolyeya Cecileê]] weke [[Şablon:J:Mîran Janbar:Qolyeya Cecileê]] guhart: Bot: Rûpel bar kir
6334641
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mîran Janbar:Qolyeya Cecileê]]
kobn2fpnooqwpk4atvnfdrnh9mydgk8
Şablon:Jêgirtin/Perwîz Cihanî/Bilîcan/2002
10
1358682
6334643
2026-08-18T11:16:47Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Perwîz Cihanî/Bilîcan/2002]] weke [[Şablon:J:Perwîz Cihanî:Bilîcan:2002]] guhart: Bot: Rûpel bar kir
6334643
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Perwîz Cihanî:Bilîcan:2002]]
2e088l9fnsbutdmg7dnd1ykthrvwug9
Şablon:Jêgirtin/Qanatê Kurdo/Tarîxa Edebyeta Kurdî/1983
10
1358683
6334645
2026-08-18T11:16:55Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Qanatê Kurdo/Tarîxa Edebyeta Kurdî/1983]] weke [[Şablon:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983]] guhart: Bot: Rûpel bar kir
6334645
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983]]
scc3pm4qkgxujuc24q3u6ggdmyri1t9
Gotûbêja şablonê:Jêgirtin/Qanatê Kurdo/Tarîxa Edebyeta Kurdî/1983
11
1358684
6334647
2026-08-18T11:16:55Z
Balyozbot
7867
Balyozbotî/ê navê [[Gotûbêja şablonê:Jêgirtin/Qanatê Kurdo/Tarîxa Edebyeta Kurdî/1983]] weke [[Gotûbêja şablonê:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983]] guhart: Bot: Rûpel bar kir
6334647
wikitext
text/x-wiki
#BERALÎKIRIN [[Gotûbêja şablonê:J:Qanatê Kurdo:Tarîxa Edebyeta Kurdî:1983]]
idp1cvcz6i4vvhddie8uybzjnnx249c
Şablon:Jêgirtin/Robert Louis Stevenson/Meseleya Seyr a Dr. Jekyll û Mr. Hyde
10
1358685
6334649
2026-08-18T11:17:02Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Robert Louis Stevenson/Meseleya Seyr a Dr. Jekyll û Mr. Hyde]] weke [[Şablon:J:Robert Louis Stevenson:Meseleya Seyr a Dr. Jekyll û Mr. Hyde]] guhart: Bot: Rûpel bar kir
6334649
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Robert Louis Stevenson:Meseleya Seyr a Dr. Jekyll û Mr. Hyde]]
fobhw5dvj4ev56sd7ca738r7lvm51cb
Şablon:Jêgirtin/Rohat Alakom/Folklor û jinên kurd/2002
10
1358686
6334651
2026-08-18T11:17:04Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Rohat Alakom/Folklor û jinên kurd/2002]] weke [[Şablon:J:Rohat Alakom:Folklor û jinên kurd:2002]] guhart: Bot: Rûpel bar kir
6334651
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Rohat Alakom:Folklor û jinên kurd:2002]]
bujbo16a9z4jgqw6e3xlrh3oktwzsnp
Şablon:Jêgirtin/Sadiq Ûskan/Gurê Manco
10
1358687
6334653
2026-08-18T11:17:06Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sadiq Ûskan/Gurê Manco]] weke [[Şablon:J:Sadiq Ûskan:Gurê Manco]] guhart: Bot: Rûpel bar kir
6334653
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Sadiq Ûskan:Gurê Manco]]
bxsdf6x7kn6hva8tla77b3haif7wq4u
Şablon:Jêgirtin/Sadiq Ûskan/Şampaz
10
1358688
6334655
2026-08-18T11:17:07Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sadiq Ûskan/Şampaz]] weke [[Şablon:J:Sadiq Ûskan:Şampaz]] guhart: Bot: Rûpel bar kir
6334655
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Sadiq Ûskan:Şampaz]]
fpt71k195badna4tlfo1zr9esjp7j9e
Şablon:Jêgirtin/Salihê Kevirbirî/Filîtê Quto/2002
10
1358689
6334657
2026-08-18T11:17:08Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Salihê Kevirbirî/Filîtê Quto/2002]] weke [[Şablon:J:Salihê Kevirbirî:Filîtê Quto:2002]] guhart: Bot: Rûpel bar kir
6334657
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Salihê Kevirbirî:Filîtê Quto:2002]]
ttltz4osjw89cdylm3ayacxc25j52u2
Şablon:Jêgirtin/Serdar Roşan/Dara gozê/2002
10
1358690
6334659
2026-08-18T11:17:10Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Serdar Roşan/Dara gozê/2002]] weke [[Şablon:J:Serdar Roşan:Dara gozê:2002]] guhart: Bot: Rûpel bar kir
6334659
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Serdar Roşan:Dara gozê:2002]]
mb88e2ajyqot0qz6ro1lv6pmjvxywfm
Şablon:Jêgirtin/Serdar Roşan/Çîroka Hezar û Şevekê
10
1358691
6334661
2026-08-18T11:17:12Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Serdar Roşan/Çîroka Hezar û Şevekê]] weke [[Şablon:J:Serdar Roşan:Çîroka Hezar û Şevekê]] guhart: Bot: Rûpel bar kir
6334661
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Serdar Roşan:Çîroka Hezar û Şevekê]]
glgbowgybdkjcyywkx5whttw1gyg6jz
Gotûbêja şablonê:Jêgirtin/Serdar Roşan/Çîroka Hezar û Şevekê
11
1358692
6334663
2026-08-18T11:17:12Z
Balyozbot
7867
Balyozbotî/ê navê [[Gotûbêja şablonê:Jêgirtin/Serdar Roşan/Çîroka Hezar û Şevekê]] weke [[Gotûbêja şablonê:J:Serdar Roşan:Çîroka Hezar û Şevekê]] guhart: Bot: Rûpel bar kir
6334663
wikitext
text/x-wiki
#BERALÎKIRIN [[Gotûbêja şablonê:J:Serdar Roşan:Çîroka Hezar û Şevekê]]
r0cw7nz02x8b60ik5y5d3xat3v9gkng
Şablon:Jêgirtin/Sidqî Hirorî/Kurê Zinarê Serbilind/1996
10
1358693
6334665
2026-08-18T11:17:13Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sidqî Hirorî/Kurê Zinarê Serbilind/1996]] weke [[Şablon:J:Sidqî Hirorî:Kurê Zinarê Serbilind:1996]] guhart: Bot: Rûpel bar kir
6334665
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Sidqî Hirorî:Kurê Zinarê Serbilind:1996]]
kcq2q8lbmwoq2ci45ty07o5q0zxjstp
Şablon:Jêgirtin/Silêman Demir/Sorê Gulê/2002
10
1358694
6334667
2026-08-18T11:17:15Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Silêman Demir/Sorê Gulê/2002]] weke [[Şablon:J:Silêman Demir:Sorê Gulê:2002]] guhart: Bot: Rûpel bar kir
6334667
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Silêman Demir:Sorê Gulê:2002]]
jhpbdvbv1drni9kvf0dl5oscu2tmrxr
Şablon:Jêgirtin/Sîma Semend/Xezal/1996
10
1358695
6334669
2026-08-18T11:17:17Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Sîma Semend/Xezal/1996]] weke [[Şablon:J:Sîma Semend:Xezal:1996]] guhart: Bot: Rûpel bar kir
6334669
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Sîma Semend:Xezal:1996]]
lgr946csiepaq5dm8yathtxsbc8on8p
Gotûbêja şablonê:Jêgirtin/Sîma Semend/Xezal/1996
11
1358696
6334671
2026-08-18T11:17:17Z
Balyozbot
7867
Balyozbotî/ê navê [[Gotûbêja şablonê:Jêgirtin/Sîma Semend/Xezal/1996]] weke [[Gotûbêja şablonê:J:Sîma Semend:Xezal:1996]] guhart: Bot: Rûpel bar kir
6334671
wikitext
text/x-wiki
#BERALÎKIRIN [[Gotûbêja şablonê:J:Sîma Semend:Xezal:1996]]
p0prsoyoqq623pe9di3o6a838bajkc7
Şablon:Jêgirtin/Tehsîn Îbrahîm Doskî/Şerha Dîwana Ehmedê Xanî
10
1358697
6334673
2026-08-18T11:17:19Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Tehsîn Îbrahîm Doskî/Şerha Dîwana Ehmedê Xanî]] weke [[Şablon:J:Tehsîn Îbrahîm Doskî:Şerha Dîwana Ehmedê Xanî]] guhart: Bot: Rûpel bar kir
6334673
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Tehsîn Îbrahîm Doskî:Şerha Dîwana Ehmedê Xanî]]
ceepk0xfy1ljw8i1hd6dai7vicsn5w4
Şablon:Jêgirtin/Têmûrê Xelîl/Nezerê Mêrxas
10
1358698
6334675
2026-08-18T11:17:20Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Têmûrê Xelîl/Nezerê Mêrxas]] weke [[Şablon:J:Têmûrê Xelîl:Nezerê Mêrxas]] guhart: Bot: Rûpel bar kir
6334675
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Têmûrê Xelîl:Nezerê Mêrxas]]
jeker225iji0ei1p683ipseexorae4a
Şablon:Jêgirtin/Xelîl Duhokî/Antolojiya Çîroka Nû ya Kurmancên Başûr
10
1358699
6334677
2026-08-18T11:17:22Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Xelîl Duhokî/Antolojiya Çîroka Nû ya Kurmancên Başûr]] weke [[Şablon:J:Xelîl Duhokî:Antolojiya Çîroka Nû ya Kurmancên Başûr]] guhart: Bot: Rûpel bar kir
6334677
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Xelîl Duhokî:Antolojiya Çîroka Nû ya Kurmancên Başûr]]
0ku36wdzk88b7cr60yygeevw9btt396
Şablon:Jêgirtin/Yaşar Kemal/Dara Hinarê
10
1358700
6334679
2026-08-18T11:17:23Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Yaşar Kemal/Dara Hinarê]] weke [[Şablon:J:Yaşar Kemal:Dara Hinarê]] guhart: Bot: Rûpel bar kir
6334679
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Yaşar Kemal:Dara Hinarê]]
cuxskwn2xt6h2sosg468agxgtslvq8z
Şablon:Jêgirtin/Yaşar Kemal/Çiyayê Agirî
10
1358701
6334681
2026-08-18T11:17:25Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Yaşar Kemal/Çiyayê Agirî]] weke [[Şablon:J:Yaşar Kemal:Çiyayê Agirî]] guhart: Bot: Rûpel bar kir
6334681
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Yaşar Kemal:Çiyayê Agirî]]
p6p6ap6gbdeys63n4q4lbewdx00z0q3
Şablon:Jêgirtin/Zeynelabidîn Zinar/Balafira Jînê
10
1358702
6334683
2026-08-18T11:17:26Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Zeynelabidîn Zinar/Balafira Jînê]] weke [[Şablon:J:Zeynelabidîn Zinar:Balafira Jînê]] guhart: Bot: Rûpel bar kir
6334683
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Zeynelabidîn Zinar:Balafira Jînê]]
2udnheskifa13w6xe4ev00lo6krsm2p
Şablon:Jêgirtin/Zeynelabidîn Zinar/Xwençe 1/1989
10
1358703
6334685
2026-08-18T11:17:28Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Zeynelabidîn Zinar/Xwençe 1/1989]] weke [[Şablon:J:Zeynelabidîn Zinar:Xwençe 1:1989]] guhart: Bot: Rûpel bar kir
6334685
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Zeynelabidîn Zinar:Xwençe 1:1989]]
9wveahgt8ye823i7n3qbqbqhh8y7ety
Şablon:Jêgirtin/Înan Eroglu/Têbîniyek/2019
10
1358704
6334687
2026-08-18T11:17:29Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Înan Eroglu/Têbîniyek/2019]] weke [[Şablon:J:Înan Eroglu:Têbîniyek:2019]] guhart: Bot: Rûpel bar kir
6334687
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Înan Eroglu:Têbîniyek:2019]]
pj4k5umjae0msip4u4ivrl99ktnmwex
Şablon:Jêgirtin/Îrfan Amîda/Şevek Şîzofren
10
1358705
6334689
2026-08-18T11:17:30Z
Balyozbot
7867
Balyozbotî/ê navê [[Şablon:Jêgirtin/Îrfan Amîda/Şevek Şîzofren]] weke [[Şablon:J:Îrfan Amîda:Şevek Şîzofren]] guhart: Bot: Rûpel bar kir
6334689
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Îrfan Amîda:Şevek Şîzofren]]
ezp7vx888rs0gdmmizlrdh6ul74j4pk
Şablon:J:Mehmed Uzun/Bîra qederê/2002
10
1358706
6334693
2026-08-18T11:22:14Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:J:Mehmed Uzun/Bîra qederê/2002]] weke [[Şablon:J:Mehmed Uzun:Bîra qederê:2002]] guhart
6334693
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun:Bîra qederê:2002]]
l5c5mxjybrfron6h5yr1x7hp9ybunxe
Şablon:J:Mehmed Uzun/Siya Evînê/1989
10
1358707
6334695
2026-08-18T11:24:16Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:J:Mehmed Uzun/Siya Evînê/1989]] weke [[Şablon:J:Mehmed Uzun:Siya Evînê:1989]] guhart
6334695
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mehmed Uzun:Siya Evînê:1989]]
sunplp13kd7q3zv36yz4nt3a2hfly7g
Şablon:jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye
10
1358708
6334700
2026-08-18T11:25:49Z
Wikihez
7344
Wikihezî/ê navê [[Şablon:jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye]] weke [[Şablon:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye]] guhart
6334700
wikitext
text/x-wiki
#BERALÎKIRIN [[Şablon:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye]]
g4u3q8aieh1511vfroyi6sm6gmwfx6y
Gotûbêja şablonê:jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye
11
1358709
6334702
2026-08-18T11:25:49Z
Wikihez
7344
Wikihezî/ê navê [[Gotûbêja şablonê:jêgirtin/Mele Mehmûdê Bayezîdî/Adat û Rusûmatnameê Ekradiye]] weke [[Gotûbêja şablonê:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye]] guhart
6334702
wikitext
text/x-wiki
#BERALÎKIRIN [[Gotûbêja şablonê:J:Mele Mehmûdê Bayezîdî:Adat û Rusûmatnameê Ekradiye]]
g0nu9ko3r6cfz3i2h47nwfcjmxjg7z0